import Foundation
import SwiftData

/// Aggregates the user's personal flight history on a specific tail
/// number. Given a registration like "N281WN", returns how many times
/// the user has flown that airframe, the routes flown on it, the
/// first/last time it appeared in history, and the most common route.
///
/// This is read-only and stateless — the store doesn't cache; every
/// call fires a fresh FetchDescriptor against ModelContext. Cheap
/// because the predicate hits the registration field directly and
/// most users will have a handful of flights per tail at most.
///
/// @MainActor because ModelContext is main-thread-only in SwiftData.
@MainActor
final class AirframeHistoryStore {

    // MARK: - Public types

    struct AirframeStats: Sendable {
        let totalFlights: Int
        /// Unique routes flown on this airframe, formatted "DAL→HOU".
        let routes: [String]
        let firstSeen: Date?
        let lastSeen: Date?
        /// The route the user has flown most often on this airframe,
        /// formatted "DAL→HOU (5 of 7)". Nil when no flights exist.
        let mostCommonRoute: String?

        static let empty = AirframeStats(
            totalFlights: 0,
            routes: [],
            firstSeen: nil,
            lastSeen: nil,
            mostCommonRoute: nil
        )
    }

    init() {}

    // MARK: - Lookup

    /// Returns aggregated stats for the given tail in the user's
    /// LoggedFlight history. Tail matching is case-insensitive — we
    /// normalize to uppercase before comparing.
    func stats(forTail registration: String, context: ModelContext) -> AirframeStats {
        let normalizedTail = registration
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .uppercased()

        guard !normalizedTail.isEmpty else {
            print("[AirframeHistory] empty tail, returning empty stats")
            return .empty
        }

        // SwiftData #Predicate can't call uppercased(), so we fetch by
        // exact case first, then fall back to a broader scan if empty.
        // In practice records are stored uppercased (importers normalize),
        // so the fast path hits.
        let predicate = #Predicate<LoggedFlight> { flight in
            flight.registration == normalizedTail
        }
        let descriptor = FetchDescriptor<LoggedFlight>(predicate: predicate)
        var matches: [LoggedFlight] = (try? context.fetch(descriptor)) ?? []

        if matches.isEmpty {
            // Fallback: scan all flights and compare uppercased. Slow
            // path, but covers legacy records that weren't normalized.
            let allDescriptor = FetchDescriptor<LoggedFlight>()
            let all = (try? context.fetch(allDescriptor)) ?? []
            matches = all.filter { flight in
                guard let reg = flight.registration else { return false }
                return reg.uppercased() == normalizedTail
            }
        }

        guard !matches.isEmpty else {
            print("[AirframeHistory] no flights found for \(normalizedTail)")
            return .empty
        }

        // Aggregate.
        let total = matches.count

        // Route strings in encounter order, deduped while preserving order.
        var seen = Set<String>()
        var orderedRoutes: [String] = []
        var routeCounts: [String: Int] = [:]

        for flight in matches {
            let route = Self.formatRoute(
                departure: flight.departureIATA,
                arrival: flight.arrivalIATA
            )
            routeCounts[route, default: 0] += 1
            if seen.insert(route).inserted {
                orderedRoutes.append(route)
            }
        }

        let dates = matches.map { $0.flightDate }
        let firstSeen = dates.min()
        let lastSeen = dates.max()

        // Most common route — break ties by alphabetical route for
        // deterministic output.
        let mostCommonRoute: String? = {
            guard let top = routeCounts
                .max(by: { lhs, rhs in
                    if lhs.value != rhs.value { return lhs.value < rhs.value }
                    return lhs.key > rhs.key
                })
            else { return nil }
            return "\(top.key) (\(top.value) of \(total))"
        }()

        print("[AirframeHistory] \(normalizedTail): \(total) flights across \(orderedRoutes.count) routes")

        return AirframeStats(
            totalFlights: total,
            routes: orderedRoutes,
            firstSeen: firstSeen,
            lastSeen: lastSeen,
            mostCommonRoute: mostCommonRoute
        )
    }

    // MARK: - Formatting

    /// "DAL→HOU" style route string. Falls back to "?" when an
    /// endpoint is missing so we never produce "→HOU" or "DAL→".
    private static func formatRoute(departure: String, arrival: String) -> String {
        let dep = departure.isEmpty ? "?" : departure.uppercased()
        let arr = arrival.isEmpty ? "?" : arrival.uppercased()
        return "\(dep)→\(arr)"
    }
}
