import Foundation

/// Aggregates per-airport load-factor signals from the bundled BTS T-100 dataset
/// so the UI can render an at-a-glance heatmap of how "open" each hub is for
/// nonrev / standby travel on a given day.
///
/// The underlying truth comes from `BTSDataStore`, which exposes flight-segment
/// records keyed by `"CARRIER_FLIGHTNUM_ORIGIN_DEST"`. For an airport's index
/// we filter to records whose origin matches the requested IATA and compute a
/// weighted average of `avgLoadFactor` using `totalFlights` as the weight —
/// busier routes count proportionally more, so a hub's score reflects its real
/// traffic mix instead of being skewed by long-tail seasonal segments.
///
/// `date` is accepted as part of the public surface so callers can later swap
/// in a date-partitioned BTS store without a signature change. The current
/// BTSDataStore returns the full bundled snapshot regardless of date; we still
/// pass it through for future use.
actor HubLoadHeatmapService {

    // MARK: - Public types

    /// A single airport's aggregated load picture.
    struct AirportLoadIndex: Sendable {
        let airport: String
        let avgLoadPct: Double
        let sampleSize: Int
        let band: LoadBand
    }

    /// Coarse buckets matching the heatmap legend.
    /// - `open`: < 0.60
    /// - `moderate`: 0.60 – 0.75
    /// - `tight`: 0.75 – 0.88
    /// - `full`: > 0.88
    enum LoadBand: Sendable {
        case open
        case moderate
        case tight
        case full

        fileprivate static func band(for loadPct: Double) -> LoadBand {
            if loadPct < 0.60 { return .open }
            if loadPct < 0.75 { return .moderate }
            if loadPct < 0.88 { return .tight }
            return .full
        }
    }

    // MARK: - Dependencies

    private let store: BTSDataStore

    // MARK: - Cache

    /// Memoized indices keyed by uppercased IATA. The bundled BTS snapshot is
    /// static at runtime, so once we've crunched a hub we can return the same
    /// answer instantly on repeat scrolls of the heatmap.
    private var cache: [String: AirportLoadIndex] = [:]

    // MARK: - Init

    init(store: BTSDataStore = BTSDataStore.shared) {
        self.store = store
    }

    // MARK: - Public API

    /// Returns the load index for `iata` or `nil` if BTSDataStore has no
    /// matching origin segments. `date` is reserved for future date-partitioned
    /// stores; the current bundled snapshot is treated as a single period.
    func loadIndex(forAirport iata: String, on date: Date) async -> AirportLoadIndex? {
        let key = iata.uppercased()

        if let cached = cache[key] {
            return cached
        }

        let allRecords = await store.allRecordsKeyed()
        guard !allRecords.isEmpty else {
            print("[HubLoadHeatmap] BTSDataStore returned no records for \(key)")
            return nil
        }

        // Filter to segments departing this airport. Composite keys are
        // "CARRIER_FLIGHTNUM_ORIGIN_DEST"; we match on the third component to
        // avoid false positives where the IATA appears inside a carrier code.
        var weightedSum: Double = 0
        var totalWeight: Int = 0
        var matchCount: Int = 0

        for (compositeKey, record) in allRecords {
            let parts = compositeKey.split(separator: "_")
            guard parts.count == 4 else { continue }
            let origin = String(parts[2])
            guard origin == key else { continue }

            let weight = record.totalFlights
            guard weight > 0 else { continue }

            weightedSum += record.avgLoadFactor * Double(weight)
            totalWeight += weight
            matchCount += 1
        }

        guard matchCount > 0, totalWeight > 0 else {
            print("[HubLoadHeatmap] No origin matches for \(key)")
            return nil
        }

        let avg = weightedSum / Double(totalWeight)
        let clamped = max(0.0, min(1.0, avg))
        let index = AirportLoadIndex(
            airport: key,
            avgLoadPct: clamped,
            sampleSize: matchCount,
            band: LoadBand.band(for: clamped)
        )

        cache[key] = index
        print("[HubLoadHeatmap] \(key) → avg=\(String(format: "%.3f", clamped)) n=\(matchCount) band=\(index.band)")
        return index
    }

    /// Clears the memoized indices. Call after BTSDataStore is rebuilt or a new
    /// snapshot is bundled.
    func invalidateCache() {
        cache.removeAll()
    }
}
