import Foundation

/// Bundled DOT/BTS historical-stats lookup.
///
/// Data is generated by ``scripts/generate_bts_bundle.py`` and shipped as
/// ``Resources/bts_bundle.json`` inside the app bundle. The JSON is a flat
/// dictionary keyed by ``CARRIER_FLIGHTNUM_ORIGIN_DEST`` (e.g.
/// ``"WN_61_DAL_HOU"``); each value is a ``BTSFlightRecord``.
///
/// The actor loads + decodes the bundle exactly once, on first access, and
/// caches it for the rest of the process lifetime. The on-disk bundle is
/// ~1-2 MB (≈8K records, real Reporting Carrier + T-100 aggregates for one
/// recent month), so decode is sub-second and the in-memory dict is cheap.
///
/// Companion file: ``Resources/bts_bundle_meta.json`` — citation metadata
/// surfaced via ``metadata()`` so the UI can label the data source.
actor BTSDataStore {

    // MARK: Singleton

    static let shared = BTSDataStore()

    // MARK: State

    private var loaded: [String: BTSFlightRecord]?
    private var loadAttempted = false
    private var meta: BTSMetadata?
    private var metaAttempted = false

    // MARK: Public API

    /// Look up the historical record for a specific carrier + flight number
    /// + origin/dest pair. Returns nil if the bundle has no entry — callers
    /// should treat that as "no data" rather than an error.
    func record(
        carrier: String,
        flightNumber: Int,
        origin: String,
        dest: String
    ) async -> BTSFlightRecord? {
        let key = Self.makeKey(
            carrier: carrier,
            flightNumber: flightNumber,
            origin: origin,
            dest: dest
        )
        return await allRecordsKeyed()[key]
    }

    /// Return the full keyed bundle. Useful for batch tools (e.g. building
    /// the upcoming "your route stats" history view).
    func allRecordsKeyed() async -> [String: BTSFlightRecord] {
        if let loaded { return loaded }
        if loadAttempted { return [:] }
        loadAttempted = true
        let parsed = Self.loadFromBundle()
        loaded = parsed
        return parsed
    }

    /// Return the citation/source metadata for the currently-bundled data.
    /// Drives the in-app "Based on DOT BTS data: <month>, <N> records" label
    /// so users can see exactly what powers the on-time / load-factor numbers.
    func metadata() async -> BTSMetadata? {
        if let meta { return meta }
        if metaAttempted { return nil }
        metaAttempted = true
        let parsed = Self.loadMetadataFromBundle()
        meta = parsed
        return parsed
    }

    // MARK: Helpers

    /// Canonical key format. Centralised so callers can't drift from the
    /// generator's format.
    static func makeKey(
        carrier: String,
        flightNumber: Int,
        origin: String,
        dest: String
    ) -> String {
        "\(carrier.uppercased())_\(flightNumber)_\(origin.uppercased())_\(dest.uppercased())"
    }

    private static func loadFromBundle() -> [String: BTSFlightRecord] {
        guard let url = Bundle.main.url(forResource: "bts_bundle", withExtension: "json") else {
            print("[BTSDataStore] bts_bundle.json not found in main bundle")
            return [:]
        }
        do {
            let data = try Data(contentsOf: url)
            let decoder = JSONDecoder()
            let parsed = try decoder.decode([String: BTSFlightRecord].self, from: data)
            print("[BTSDataStore] loaded \(parsed.count) records from bts_bundle.json")
            return parsed
        } catch {
            print("[BTSDataStore] failed to decode bts_bundle.json: \(error)")
            Task { @MainActor in
                DataIntegrityMonitor.shared.report("bts_bundle.json", error: error)
            }
            return [:]
        }
    }

    private static func loadMetadataFromBundle() -> BTSMetadata? {
        guard let url = Bundle.main.url(forResource: "bts_bundle_meta", withExtension: "json") else {
            print("[BTSDataStore] bts_bundle_meta.json not found in main bundle")
            return nil
        }
        do {
            let data = try Data(contentsOf: url)
            let parsed = try JSONDecoder().decode(BTSMetadata.self, from: data)
            print("[BTSDataStore] loaded metadata: \(parsed.sourcePeriod), \(parsed.recordCount) records")
            return parsed
        } catch {
            print("[BTSDataStore] failed to decode bts_bundle_meta.json: \(error)")
            return nil
        }
    }
}

// MARK: - Record type

/// A single historical-performance record for one carrier + flight number
/// + origin/dest pair. All fields are aggregated over ``samplePeriod`` (e.g.
/// "2026-02"). See ``bts_bundle_meta.json`` for the source URLs + methodology.
struct BTSFlightRecord: Sendable, Codable {
    /// Total number of operated flights observed in the sample period.
    let totalFlights: Int

    /// Fraction of those flights that arrived on time, BTS definition:
    /// arrival delay <= 15 minutes (0...1).
    let onTimePct: Double

    /// Mean arrival delay in minutes, averaged across delayed arrivals
    /// (negative = early arrivals are excluded; matches BTS convention).
    let avgDelayMin: Double

    /// Fraction of scheduled flights cancelled (0...1).
    let cancelledPct: Double

    /// Average load factor — fraction of seats sold, from T-100 Domestic
    /// Segment data (sum PASSENGERS / sum SEATS at the carrier+route
    /// level — not per flight number, since T-100 does not split by
    /// flight number).
    let avgLoadFactor: Double

    /// Average seat count per departure on this carrier+route, from T-100.
    /// Used to scale predictions when live equipment differs.
    let avgSeats: Int

    /// ISO-like tag describing the sample period (e.g. "2026-02").
    let samplePeriod: String
}

// MARK: - Metadata type

/// Citation block for the bundled data. Read at runtime from
/// ``bts_bundle_meta.json`` so the UI can show users exactly which BTS
/// month + source URLs power the on-time + load-factor numbers.
struct BTSMetadata: Sendable, Codable {
    /// Calendar month covered (e.g. "2026-02").
    let sourcePeriod: String

    /// When this bundle was generated by ``scripts/generate_bts_bundle.py``.
    let downloadedAt: String

    /// Direct URLs to the BTS tables we pulled.
    let sourceURLs: [String]

    /// Number of (carrier, flight#, origin, dest) records in ``bts_bundle.json``.
    let recordCount: Int

    /// Carriers represented in the bundle.
    let carriers: [String]

    /// Minimum operated-flights filter applied during aggregation. Records
    /// below this volume are dropped to reduce statistical noise.
    let minFlightsFilter: Int

    /// Methodology notes — surfaced verbatim in the in-app "Data source" sheet.
    let notes: String

    /// Bumped whenever the on-disk shape changes so old caches can be invalidated.
    let schemaVersion: Int
}
