import XCTest
import SwiftData
@testable import Flights

/// Unit tests for `AirframeHistoryStore`.
///
/// We exercise the store against an in-memory `ModelContainer` seeded
/// with `LoggedFlight` rows that vary by tail number, route, and date.
/// All assertions reference the documented `AirframeStats` contract.
@MainActor
final class AirframeHistoryStoreTests: XCTestCase {

    private var container: ModelContainer!
    private var context: ModelContext!
    private var store: AirframeHistoryStore!

    override func setUpWithError() throws {
        try super.setUpWithError()
        let schema = Schema([LoggedFlight.self])
        let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
        container = try ModelContainer(for: schema, configurations: config)
        context = ModelContext(container)
        store = AirframeHistoryStore()
    }

    override func tearDownWithError() throws {
        store = nil
        context = nil
        container = nil
        try super.tearDownWithError()
    }

    // MARK: - Helpers

    private static let epoch = Date(timeIntervalSince1970: 1_700_000_000)

    private func date(_ dayOffset: Int) -> Date {
        Self.epoch.addingTimeInterval(TimeInterval(dayOffset) * 86_400)
    }

    @discardableResult
    private func insert(
        registration: String?,
        origin: String,
        dest: String,
        flightDate: Date
    ) -> LoggedFlight {
        let flight = LoggedFlight(
            flightDate: flightDate,
            departureIATA: origin,
            arrivalIATA: dest,
            registration: registration
        )
        context.insert(flight)
        return flight
    }

    // MARK: - Tests

    /// Empty store → empty stats sentinel.
    func test_stats_emptyContext_returnsEmpty() {
        let stats = store.stats(forTail: "N281WN", context: context)

        XCTAssertEqual(stats.totalFlights, 0)
        XCTAssertTrue(stats.routes.isEmpty)
        XCTAssertNil(stats.firstSeen)
        XCTAssertNil(stats.lastSeen)
        XCTAssertNil(stats.mostCommonRoute)
    }

    /// 3 flights on the same tail across 2 distinct routes — verify the
    /// aggregate counts and the "DAL→HOU (2 of 3)" most-common-route
    /// formatting.
    func test_stats_threeFlightsTwoRoutes_aggregatesCorrectly() {
        insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(0))
        insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(5))
        insert(registration: "N281WN", origin: "DAL", dest: "LAS", flightDate: date(10))
        // Other-tail noise — must not be counted.
        insert(registration: "N999AA", origin: "DAL", dest: "HOU", flightDate: date(2))

        let stats = store.stats(forTail: "N281WN", context: context)

        XCTAssertEqual(stats.totalFlights, 3)
        XCTAssertEqual(Set(stats.routes), Set(["DAL→HOU", "DAL→LAS"]))
        XCTAssertEqual(stats.routes.count, 2)
        XCTAssertEqual(stats.firstSeen, date(0))
        XCTAssertEqual(stats.lastSeen, date(10))
        XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (2 of 3)")
    }

    /// Lookup tail must be normalized to uppercase — passing "n281wn"
    /// matches a stored "N281WN".
    func test_stats_lookupIsCaseInsensitive() {
        insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(0))

        let stats = store.stats(forTail: "n281wn", context: context)

        XCTAssertEqual(stats.totalFlights, 1)
        XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (1 of 1)")
    }

    /// The store should still report stats for a single-flight tail. The
    /// History UI hides the section in that case, but the underlying
    /// store contract returns the real count.
    func test_stats_singleFlight_returnsTotalOne() {
        insert(registration: "N281WN", origin: "DAL", dest: "HOU", flightDate: date(0))

        let stats = store.stats(forTail: "N281WN", context: context)

        XCTAssertEqual(stats.totalFlights, 1)
        XCTAssertEqual(stats.routes, ["DAL→HOU"])
        XCTAssertEqual(stats.firstSeen, date(0))
        XCTAssertEqual(stats.lastSeen, date(0))
        XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (1 of 1)")
    }

    /// Mixed-case stored registration: a record persisted with lowercase
    /// "n281wn" must still be discoverable when callers ask for
    /// "N281WN". Today the fast-path #Predicate misses (it compares
    /// exact bytes against the uppercased query) and the fallback
    /// table-scan recovers it. After Phase 3 fixes registration
    /// normalisation at write-time (or switches to a case-insensitive
    /// predicate), the fast path will hit — but this test should still
    /// pass either way.
    func test_stats_lowercaseStoredRegistration_isFoundViaFallback() {
        insert(registration: "n281wn", origin: "DAL", dest: "HOU", flightDate: date(0))

        let stats = store.stats(forTail: "N281WN", context: context)

        XCTAssertEqual(stats.totalFlights, 1)
        XCTAssertEqual(stats.routes, ["DAL→HOU"])
        XCTAssertEqual(stats.mostCommonRoute, "DAL→HOU (1 of 1)")
    }
}
