import SwiftUI
import MapKit
import CoreLocation

struct LiveFlightsView: View {
    let openSky: OpenSkyClient
    let database: AirportDatabase

    // MARK: - Map state

    @State private var position: MapCameraPosition = .automatic
    @State private var visibleRegion: MKCoordinateRegion?

    // MARK: - Data state

    @State private var aircraft: [LiveAircraft] = []
    @State private var lastFetchAt: Date?
    @State private var nextFetchAllowedAt: Date = .distantPast
    @State private var isLoading = false
    @State private var error: String?

    // MARK: - Filters

    @State private var searchText: String = ""
    @State private var selectedAirlineICAO: Set<String> = []
    @State private var selectedCategories: Set<Int> = []
    @State private var hideOnGround: Bool = false

    // MARK: - Selection

    @State private var selectedAircraft: LiveAircraft?

    // Refresh interval — paired with the rate-limit guard. Anonymous OpenSky
    // is 100/day so we keep the auto-refresh tab-conservative.
    private static let refreshInterval: TimeInterval = 15

    var body: some View {
        ZStack(alignment: .top) {
            mapLayer
            overlayHeader
            footerBar
        }
        .ignoresSafeArea(.container, edges: .bottom)
        .task {
            await initialFetch()
        }
        .task(id: refreshTick) {
            // Auto-refresh tick — only fires after the previous task completes.
            try? await Task.sleep(nanoseconds: UInt64(Self.refreshInterval * 1_000_000_000))
            await refreshIfAllowed()
        }
        .sheet(item: $selectedAircraft) { ac in
            LiveFlightDetailSheet(
                aircraft: ac,
                openSky: openSky,
                database: database
            )
            .presentationDetents([.medium, .large])
            .presentationDragIndicator(.visible)
        }
    }

    // MARK: - Map

    private var mapLayer: some View {
        Map(position: $position, selection: $selectedAircraft.iconSelection) {
            ForEach(filteredAircraft) { ac in
                Annotation(ac.trimmedCallsign ?? ac.icao24, coordinate: ac.coordinate) {
                    AircraftPin(ac: ac, isSelected: selectedAircraft?.id == ac.id)
                        .onTapGesture {
                            selectedAircraft = ac
                        }
                }
                .annotationTitles(.hidden)
            }
        }
        .mapStyle(.standard(elevation: .flat))
        .onMapCameraChange(frequency: .onEnd) { context in
            visibleRegion = context.region
            // Refetch when the user pans/zooms to a new area.
            Task { await refreshIfAllowed() }
        }
    }

    // MARK: - Header (filters + search)

    private var overlayHeader: some View {
        VStack(spacing: 8) {
            HStack(spacing: 8) {
                Image(systemName: "magnifyingglass")
                    .foregroundStyle(FlightTheme.textSecondary)
                TextField("Search callsign or flight (e.g. AA2178)", text: $searchText)
                    .autocorrectionDisabled()
                    .textInputAutocapitalization(.characters)
                    .onSubmit { centerOnSearchMatch() }

                if !searchText.isEmpty {
                    Button { searchText = "" } label: {
                        Image(systemName: "xmark.circle.fill")
                            .foregroundStyle(FlightTheme.textSecondary)
                    }
                    .buttonStyle(.plain)
                }
            }
            .padding(.horizontal, 14)
            .padding(.vertical, 10)
            .background(FlightTheme.cardBackground)
            .clipShape(Capsule())
            .shadow(color: FlightTheme.cardShadow, radius: 6, y: 2)

            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 8) {
                    FilterChip(
                        label: hideOnGround ? "Airborne only" : "Include ground",
                        systemImage: hideOnGround ? "airplane" : "airplane.circle",
                        isActive: hideOnGround
                    ) { hideOnGround.toggle() }

                    Menu {
                        Section("Airline") {
                            ForEach(visibleAirlines, id: \.icao) { item in
                                Button {
                                    toggle(&selectedAirlineICAO, item.icao)
                                } label: {
                                    if selectedAirlineICAO.contains(item.icao) {
                                        Label(item.label, systemImage: "checkmark")
                                    } else {
                                        Text(item.label)
                                    }
                                }
                            }
                            if !selectedAirlineICAO.isEmpty {
                                Button("Clear", role: .destructive) { selectedAirlineICAO.removeAll() }
                            }
                        }
                    } label: {
                        FilterChipLabel(
                            label: selectedAirlineICAO.isEmpty
                                ? "Airline"
                                : "Airline · \(selectedAirlineICAO.count)",
                            systemImage: "building.2",
                            isActive: !selectedAirlineICAO.isEmpty
                        )
                    }

                    Menu {
                        Section("Aircraft type") {
                            ForEach(visibleCategories, id: \.code) { item in
                                Button {
                                    toggle(&selectedCategories, item.code)
                                } label: {
                                    if selectedCategories.contains(item.code) {
                                        Label(item.label, systemImage: "checkmark")
                                    } else {
                                        Text(item.label)
                                    }
                                }
                            }
                            if !selectedCategories.isEmpty {
                                Button("Clear", role: .destructive) { selectedCategories.removeAll() }
                            }
                        }
                    } label: {
                        FilterChipLabel(
                            label: selectedCategories.isEmpty
                                ? "Type"
                                : "Type · \(selectedCategories.count)",
                            systemImage: "airplane.departure",
                            isActive: !selectedCategories.isEmpty
                        )
                    }

                    if !selectedAirlineICAO.isEmpty || !selectedCategories.isEmpty || hideOnGround {
                        Button {
                            selectedAirlineICAO.removeAll()
                            selectedCategories.removeAll()
                            hideOnGround = false
                        } label: {
                            FilterChipLabel(label: "Reset", systemImage: "arrow.counterclockwise", isActive: false)
                        }
                    }
                }
                .padding(.horizontal, 4)
            }
        }
        .padding(.horizontal, 12)
        .padding(.top, 8)
    }

    // MARK: - Footer (count + refresh status)

    private var footerBar: some View {
        VStack {
            Spacer()
            HStack(spacing: 12) {
                if isLoading {
                    ProgressView().tint(.white)
                } else {
                    Image(systemName: "antenna.radiowaves.left.and.right")
                        .foregroundStyle(.white)
                }
                Text("\(filteredAircraft.count) aircraft")
                    .font(.subheadline.weight(.semibold))
                    .foregroundStyle(.white)
                if let last = lastFetchAt {
                    Text("· updated \(relativeTime(last))")
                        .font(.caption)
                        .foregroundStyle(.white.opacity(0.8))
                }
                Spacer()
                Button {
                    Task { await refreshNow() }
                } label: {
                    Image(systemName: "arrow.clockwise")
                        .foregroundStyle(.white)
                }
                .disabled(isLoading)
                .opacity(isLoading ? 0.3 : 1)
            }
            .padding(.horizontal, 16)
            .padding(.vertical, 12)
            .background(Color.black.opacity(0.6), in: Capsule())
            .padding(.horizontal, 12)
            .padding(.bottom, 28)

            if let error {
                Text(error)
                    .font(.caption)
                    .foregroundStyle(.white)
                    .padding(.horizontal, 12)
                    .padding(.vertical, 6)
                    .background(FlightTheme.cancelled, in: Capsule())
                    .padding(.bottom, 12)
            }
        }
    }

    // MARK: - Derived

    private var refreshTick: Int {
        // Returning the count of `aircraft` makes the .task(id:) fire again
        // every time the data changes, scheduling the next refresh.
        aircraft.count &+ (isLoading ? 1 : 0)
    }

    private var filteredAircraft: [LiveAircraft] {
        let s = searchText.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        return aircraft.filter { ac in
            if hideOnGround && ac.onGround { return false }
            if !selectedAirlineICAO.isEmpty,
               let code = ac.airlineICAO,
               !selectedAirlineICAO.contains(code) {
                return false
            }
            if !selectedCategories.isEmpty {
                guard let cat = ac.category, selectedCategories.contains(cat) else { return false }
            }
            if !s.isEmpty {
                let cs = ac.trimmedCallsign?.uppercased() ?? ""
                if !cs.contains(s) { return false }
            }
            return true
        }
    }

    private struct AirlineFilterItem: Hashable { let icao: String; let label: String }
    private var visibleAirlines: [AirlineFilterItem] {
        var seen: [String: Int] = [:]
        for ac in aircraft {
            if let code = ac.airlineICAO {
                seen[code, default: 0] += 1
            }
        }
        return seen.keys.sorted().map { icao in
            let info = AircraftRegistry.airline(forICAO: icao)
            let count = seen[icao] ?? 0
            let name = info.name
            return AirlineFilterItem(icao: icao, label: "\(name) (\(count))")
        }
    }

    private struct CategoryFilterItem: Hashable { let code: Int; let label: String }
    private var visibleCategories: [CategoryFilterItem] {
        var seen: [Int: Int] = [:]
        for ac in aircraft {
            if let c = ac.category, c > 0 {
                seen[c, default: 0] += 1
            }
        }
        return seen.keys.sorted().compactMap { code in
            guard let name = aircraftCategoryName(code) else { return nil }
            let count = seen[code] ?? 0
            return CategoryFilterItem(code: code, label: "\(name) (\(count))")
        }
    }

    // MARK: - Fetch

    private func initialFetch() async {
        if visibleRegion == nil {
            // Default to a US-centered view if no camera yet.
            let initial = MKCoordinateRegion(
                center: CLLocationCoordinate2D(latitude: 39.5, longitude: -98.0),
                span: MKCoordinateSpan(latitudeDelta: 30, longitudeDelta: 50)
            )
            position = .region(initial)
            visibleRegion = initial
        }
        await refreshNow()
    }

    private func refreshIfAllowed() async {
        guard Date() >= nextFetchAllowedAt else { return }
        await refreshNow()
    }

    private func refreshNow() async {
        guard !isLoading, let r = visibleRegion else { return }
        isLoading = true
        defer { isLoading = false }

        let (latMin, lonMin, latMax, lonMax) = boundingBox(of: r)
        do {
            let results = try await openSky.states(
                latMin: latMin, lonMin: lonMin, latMax: latMax, lonMax: lonMax
            )
            aircraft = results
            lastFetchAt = Date()
            nextFetchAllowedAt = Date().addingTimeInterval(Self.refreshInterval)
            error = nil
        } catch {
            self.error = (error as? OpenSkyClient.ClientError)?.errorDescription
                ?? error.localizedDescription
            // Back off harder on throttling.
            if case OpenSkyClient.ClientError.throttled = error {
                nextFetchAllowedAt = Date().addingTimeInterval(60)
            }
        }
    }

    private func boundingBox(of r: MKCoordinateRegion) -> (Double, Double, Double, Double) {
        let lat = r.center.latitude
        let lon = r.center.longitude
        let dLat = r.span.latitudeDelta / 2
        let dLon = r.span.longitudeDelta / 2
        return (
            max(-90,  lat - dLat),
            max(-180, lon - dLon),
            min( 90,  lat + dLat),
            min( 180, lon + dLon)
        )
    }

    // MARK: - Search / selection helpers

    private func centerOnSearchMatch() {
        let s = searchText.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        guard let match = aircraft.first(where: {
            ($0.trimmedCallsign?.uppercased() ?? "").contains(s)
        }) else { return }
        selectedAircraft = match
        position = .region(MKCoordinateRegion(
            center: match.coordinate,
            span: MKCoordinateSpan(latitudeDelta: 4, longitudeDelta: 6)
        ))
    }

    private func toggle<T: Hashable>(_ set: inout Set<T>, _ value: T) {
        if set.contains(value) { set.remove(value) } else { set.insert(value) }
    }

    private func relativeTime(_ d: Date) -> String {
        let secs = Int(Date().timeIntervalSince(d))
        if secs < 5 { return "just now" }
        if secs < 60 { return "\(secs)s ago" }
        return "\(secs / 60)m ago"
    }
}

// MARK: - Aircraft pin

private struct AircraftPin: View {
    let ac: LiveAircraft
    let isSelected: Bool

    var body: some View {
        ZStack {
            if isSelected {
                Circle()
                    .fill(FlightTheme.accent.opacity(0.25))
                    .frame(width: 36, height: 36)
            }
            Image(systemName: "airplane")
                .font(.system(size: isSelected ? 18 : 14, weight: .bold))
                .foregroundStyle(.white)
                .padding(6)
                .background(
                    Circle().fill(tint)
                )
                .rotationEffect(.degrees(Double(ac.heading ?? 0) - 45))
                // SF Symbol "airplane" points up-and-right by default;
                // -45° aligns it to true north before applying the heading.
        }
        .contentShape(Rectangle())
    }

    private var tint: Color {
        if ac.onGround { return FlightTheme.textTertiary }
        switch ac.verticalState {
        case .climbing:   return FlightTheme.onTime
        case .descending: return FlightTheme.delayed
        case .level:      return FlightTheme.accent
        }
    }
}

// MARK: - Filter chips

private struct FilterChip: View {
    let label: String
    let systemImage: String
    let isActive: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            FilterChipLabel(label: label, systemImage: systemImage, isActive: isActive)
        }
        .buttonStyle(.plain)
    }
}

private struct FilterChipLabel: View {
    let label: String
    let systemImage: String
    let isActive: Bool

    var body: some View {
        HStack(spacing: 6) {
            Image(systemName: systemImage)
                .font(.caption)
            Text(label)
                .font(.caption.weight(.semibold))
        }
        .foregroundStyle(isActive ? .white : FlightTheme.textPrimary)
        .padding(.horizontal, 12)
        .padding(.vertical, 8)
        .background(
            Capsule()
                .fill(isActive ? FlightTheme.accent : FlightTheme.cardBackground)
        )
        .shadow(color: FlightTheme.cardShadow, radius: 4, y: 1)
    }
}

// MARK: - Optional<Identifiable> helper

private extension Binding where Value == LiveAircraft? {
    /// Swift's Map(selection:) wants a Binding<Value?> where Value is Hashable.
    /// LiveAircraft is Hashable + Identifiable so this just forwards through.
    var iconSelection: Binding<LiveAircraft?> { self }
}
