import SwiftUI

/// Home tab. One unified search.
///
/// - With a destination set: route-explorer `/route` returns directs + 1/2-stop
///   connections; results render as `ConnectionRow`s.
/// - With destination blank: route-explorer `/departures` (maxStops:0) returns
///   every flight leaving the origin; results render as compact `DepartureLegRow`s
///   filtered by the chosen time window.
///
/// Either way, tapping a result opens `ConnectionLoadDetailView`, which fans
/// load fetches across each leg in parallel and offers per-leg drill-down to
/// `FlightLoadDetailView` for waitlists / passenger lists.
struct RoutePlannerView: View {
    let database: AirportDatabase
    /// Retained for the "Where can I go?" path (no destination), which
    /// still needs the route-explorer `/departures` endpoint. Direct
    /// searches (destination set) now flow through ``flightAware``.
    let client: RouteExplorerClient
    /// Direct-flight schedule lookup via FlightAware. No Cloudflare
    /// Turnstile, no auth — used whenever a destination is set.
    let flightAware: FlightAwareScheduleClient
    let loadService: AirlineLoadService

    // MARK: - Inputs

    @State private var origin: MapAirport?
    @State private var destination: MapAirport?
    @State private var date: Date = Date()

    // Connection-mode controls (visible only when destination is set)
    @State private var maxStops: Int = 1
    @State private var connectionSort: RouteSortOption = .departureEarliest
    @State private var includeInterline: Bool = false

    // "Where can I go?" controls (visible only when destination is blank)
    @State private var windowHours: Int = 6
    @State private var referenceDate: Date = Date()
    @State private var departureSort: RouteSortOption = .departureEarliest

    // MARK: - Search state

    @State private var isLoading: Bool = false
    @State private var error: String?
    @State private var connections: [RouteConnection] = []
    @State private var appendix: RouteAppendix?

    @State private var pendingSheet: ConnectionLoadRequest?

    /// Set to true when a search hits the route-explorer clearance gate
    /// (`/api/token` → 403 `reason:"clearance"`). Drives presentation of
    /// `RouteExplorerGateSheet`; on its dismiss we automatically re-run
    /// the search.
    @State private var showClearanceGate: Bool = false

    /// Set to a URL when the user taps "Open in route-explorer" — pops a
    /// fullscreen ``RouteExplorerBrowserView`` (SFSafariViewController)
    /// so they can use the original site directly inside the app.
    @State private var routeExplorerBrowserURL: URL?

    private var hasDestination: Bool { destination != nil }
    private var canSearch: Bool { origin != nil }

    // MARK: - Body

    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(alignment: .leading, spacing: FlightTheme.sectionSpacing) {
                    airportsCard
                    dateCard
                    if hasDestination {
                        connectionControls
                    } else {
                        whereCanIGoControls
                    }
                    searchButton
                    openInRouteExplorerButton
                    sortBar
                    resultsHeader
                    resultsList
                }
                .padding(.horizontal)
                .padding(.vertical, 12)
            }
            .background(FlightTheme.background.ignoresSafeArea())
            .navigationTitle("Flights")
            .sheet(item: $pendingSheet) { req in
                ConnectionLoadDetailView(
                    connection: req.connection,
                    appendix: req.appendix,
                    database: database,
                    loadService: loadService
                )
            }
            .sheet(isPresented: $showClearanceGate) {
                // Once user passes Turnstile (cookie lands in the shared
                // WKWebsiteDataStore), the sheet auto-dismisses. We then
                // re-fire the search, which now goes through cleanly.
                RouteExplorerGateSheet()
                    .onDisappear {
                        Task { await runSearch() }
                    }
            }
            .fullScreenCover(item: $routeExplorerBrowserURL) { url in
                NavigationStack {
                    RouteExplorerBrowserView(url: url)
                        .ignoresSafeArea()
                        .toolbar {
                            ToolbarItem(placement: .cancellationAction) {
                                Button("Done") { routeExplorerBrowserURL = nil }
                            }
                        }
                }
            }
        }
    }

    // MARK: - Airports + date

    private var airportsCard: some View {
        VStack(alignment: .leading, spacing: 0) {
            VStack(alignment: .leading, spacing: 12) {
                Label {
                    Text("FROM")
                        .font(FlightTheme.label())
                        .foregroundStyle(.secondary)
                        .tracking(1)
                } icon: {
                    Image(systemName: "airplane.departure")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                IATAAirportPicker(
                    label: "Origin (IATA or city)",
                    selection: $origin,
                    database: database
                )
            }
            .padding(FlightTheme.cardPadding)

            Divider().padding(.horizontal, FlightTheme.cardPadding)

            VStack(alignment: .leading, spacing: 12) {
                Label {
                    Text("TO (OPTIONAL)")
                        .font(FlightTheme.label())
                        .foregroundStyle(.secondary)
                        .tracking(1)
                } icon: {
                    Image(systemName: "mappin.and.ellipse")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                IATAAirportPicker(
                    label: "Leave blank for \"where can I go?\"",
                    selection: $destination,
                    database: database
                )
            }
            .padding(FlightTheme.cardPadding)
        }
        .background(FlightTheme.cardBackground)
        .clipShape(RoundedRectangle(cornerRadius: FlightTheme.cardCornerRadius))
        .shadow(color: FlightTheme.cardShadow, radius: 8, y: 2)
    }

    private var dateCard: some View {
        HStack(spacing: 10) {
            Image(systemName: "calendar")
                .foregroundStyle(FlightTheme.accent)
                .font(.body)
            DatePicker(
                hasDestination ? "Travel Date" : "Day to search",
                selection: $date,
                displayedComponents: .date
            )
            .labelsHidden()
            .datePickerStyle(.compact)
            .tint(FlightTheme.accent)
            Spacer()
        }
        .flightCard()
    }

    // MARK: - Mode-specific controls

    private var connectionControls: some View {
        VStack(alignment: .leading, spacing: 10) {
            Text("MAX STOPS")
                .font(FlightTheme.label())
                .foregroundStyle(.secondary)
                .tracking(1)
            Picker("Max stops", selection: $maxStops) {
                Text("Direct").tag(0)
                Text("1 stop").tag(1)
                Text("2 stops").tag(2)
            }
            .pickerStyle(.segmented)

            Toggle(isOn: $includeInterline) {
                Text("Interline carriers only").font(.subheadline)
            }
            .padding(.top, 4)
            .tint(FlightTheme.accent)
        }
        .flightCard()
    }

    private var whereCanIGoControls: some View {
        VStack(alignment: .leading, spacing: 10) {
            Text("DEPARTING WITHIN")
                .font(FlightTheme.label())
                .foregroundStyle(.secondary)
                .tracking(1)
            Picker("Window", selection: $windowHours) {
                Text("2h").tag(2)
                Text("4h").tag(4)
                Text("6h").tag(6)
                Text("12h").tag(12)
                Text("24h").tag(24)
            }
            .pickerStyle(.segmented)

            HStack(spacing: 10) {
                Image(systemName: "clock")
                    .foregroundStyle(FlightTheme.accent)
                    .font(.body)
                DatePicker(
                    "From",
                    selection: $referenceDate,
                    displayedComponents: [.date, .hourAndMinute]
                )
                .labelsHidden()
                .datePickerStyle(.compact)
                .tint(FlightTheme.accent)
                Spacer()
                Button("Now") {
                    referenceDate = Date()
                }
                .font(.caption.weight(.semibold))
                .buttonStyle(.borderedProminent)
                .tint(FlightTheme.accent.opacity(0.2))
                .foregroundStyle(FlightTheme.accent)
            }
            .padding(.top, 6)
        }
        .flightCard()
    }

    // MARK: - Search button

    /// Toolbar-style button under the main Search action that pops the
    /// embedded route-explorer browser. The only viable path to
    /// multi-stop / where-can-I-go-with-times, since our in-app WKWebView
    /// can't pass Turnstile but SFSafariViewController can.
    private var openInRouteExplorerButton: some View {
        Button {
            routeExplorerBrowserURL = makeRouteExplorerURL()
        } label: {
            HStack(spacing: 6) {
                Image(systemName: "globe")
                Text("Open in route-explorer")
                    .font(.footnote.weight(.semibold))
            }
            .foregroundStyle(FlightTheme.accent)
            .padding(.vertical, 8)
            .frame(maxWidth: .infinity)
            .background(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(FlightTheme.accent.opacity(0.4), lineWidth: 1)
            )
        }
    }

    /// Build the deep link into route-explorer.com using whatever fields
    /// the user has filled. Falls back to the homepage if the user
    /// hasn't picked an origin yet.
    private func makeRouteExplorerURL() -> URL {
        guard let origin else {
            return URL(string: "https://route-explorer.com/")!
        }
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd"
        df.timeZone = TimeZone(identifier: "UTC")
        var comps = URLComponents(string: "https://route-explorer.com/")!
        var items: [URLQueryItem] = [
            URLQueryItem(name: "from", value: origin.iata),
            URLQueryItem(name: "date", value: df.string(from: date)),
        ]
        if let destination {
            items.append(URLQueryItem(name: "to", value: destination.iata))
        }
        comps.queryItems = items
        return comps.url ?? URL(string: "https://route-explorer.com/")!
    }

    private var searchButton: some View {
        Button {
            Task { await runSearch() }
        } label: {
            HStack {
                if isLoading {
                    ProgressView().tint(.white)
                } else {
                    Image(systemName: hasDestination ? "magnifyingglass" : "questionmark.diamond")
                }
                Text(searchButtonText).fontWeight(.bold)
            }
            .foregroundStyle(.white)
            .frame(maxWidth: .infinity)
            .frame(height: 50)
            .background(
                LinearGradient(
                    colors: [FlightTheme.accent, FlightTheme.accentLight],
                    startPoint: .leading,
                    endPoint: .trailing
                )
            )
            .clipShape(RoundedRectangle(cornerRadius: 12))
        }
        .disabled(!canSearch || isLoading)
        .opacity(canSearch && !isLoading ? 1.0 : 0.5)
    }

    private var searchButtonText: String {
        if isLoading { return "Loading..." }
        return hasDestination ? "Search Routes" : "Where can I go?"
    }

    // MARK: - Sort bar

    /// SORT BY picker, slotted between the search button and the results.
    /// Hidden until there's something to reorder so the empty home isn't
    /// cluttered with a control that doesn't apply yet.
    @ViewBuilder
    private var sortBar: some View {
        if hasDestination, !sortedConnections.isEmpty {
            sortPicker(
                options: RouteSortOption.connectionOptions,
                selection: $connectionSort
            )
        } else if !hasDestination, !filteredFlights.isEmpty {
            sortPicker(
                options: RouteSortOption.departureOptions,
                selection: $departureSort
            )
        }
    }

    private func sortPicker(
        options: [RouteSortOption],
        selection: Binding<RouteSortOption>
    ) -> some View {
        HStack(spacing: 8) {
            Text("SORT BY")
                .font(FlightTheme.label())
                .foregroundStyle(.secondary)
                .tracking(1)
            Spacer()
            Picker("Sort by", selection: selection) {
                ForEach(options, id: \.self) { option in
                    Text(option.label).tag(option)
                }
            }
            .pickerStyle(.menu)
            .tint(FlightTheme.accent)
        }
    }

    // MARK: - Results

    @ViewBuilder
    private var resultsHeader: some View {
        if let error {
            ContentUnavailableView {
                Label("No results", systemImage: "exclamationmark.triangle")
            } description: {
                Text(error)
            } actions: {
                Button("Retry") {
                    Task { await runSearch() }
                }
                .buttonStyle(.borderedProminent)
                .tint(FlightTheme.accent)
            }
        } else if hasDestination, !sortedConnections.isEmpty {
            HStack {
                Text("\(sortedConnections.count) itinerar\(sortedConnections.count == 1 ? "y" : "ies")")
                    .font(.subheadline.weight(.semibold))
                    .foregroundStyle(FlightTheme.textPrimary)
                Spacer()
                let pastDropped = connections.count - sortedConnections.count
                if pastDropped > 0 {
                    Text("\(pastDropped) already departed")
                        .font(.caption)
                        .foregroundStyle(FlightTheme.textSecondary)
                }
            }
        } else if !hasDestination, !filteredFlights.isEmpty {
            HStack {
                Text("\(filteredFlights.count) departure\(filteredFlights.count == 1 ? "" : "s")")
                    .font(.subheadline.weight(.semibold))
                    .foregroundStyle(FlightTheme.textPrimary)
                Spacer()
                Text("next \(windowHours)h")
                    .font(.caption)
                    .foregroundStyle(FlightTheme.textSecondary)
            }
        }
    }

    /// Connection-mode results: drop any connection whose first leg has
    /// already departed (the API doesn't accept a from-time floor — it
    /// just returns the earliest 500 of the calendar day, which on a
    /// same-day search is mostly already-past flights), then re-sort
    /// per the user's pick.
    private var sortedConnections: [RouteConnection] {
        let now = Date()
        return connections
            .filter { $0.firstDeparture > now }
            .sorted(by: connectionSort)
    }

    @ViewBuilder
    private var resultsList: some View {
        if hasDestination {
            ForEach(sortedConnections) { connection in
                ConnectionRow(
                    connection: connection,
                    appendix: appendix,
                    database: database
                ) { _ in
                    openConnection(connection)
                }
            }
        } else {
            ForEach(filteredFlights, id: \.id) { leg in
                Button {
                    openSingleLeg(leg)
                } label: {
                    DepartureLegRow(
                        leg: leg,
                        appendix: appendix,
                        database: database,
                        referenceDate: referenceDate
                    )
                }
                .buttonStyle(.plain)
            }
        }
    }

    /// Where-can-I-go results: flatten connections (each is a single leg
    /// since maxStops:0), filter to the chosen window, and apply the
    /// user's chosen sort.
    private var filteredFlights: [RouteFlight] {
        let windowEnd = referenceDate.addingTimeInterval(TimeInterval(windowHours * 3600))
        return connections
            .flatMap { $0.flights }
            .filter { leg in
                let dep = leg.departure.dateTime
                return dep >= referenceDate && dep <= windowEnd
            }
            .sorted(by: departureSort)
    }

    // MARK: - Search action

    private func runSearch() async {
        guard let origin else { return }
        isLoading = true
        error = nil
        connections = []
        appendix = nil

        do {
            if let destination {
                // Direct mode → FlightAware. Connection-finding (multi-stop)
                // is intentionally out of scope here: FlightAware exposes
                // per-flight schedule, not joined itineraries, and replacing
                // the route-explorer multi-stop solver isn't a v1 goal.
                // The maxStops segmented picker is retained in the UI but
                // the search itself is direct-only.
                let result = try await flightAware.searchDirectFlights(
                    from: origin.iata,
                    to: destination.iata,
                    date: date
                )
                self.connections = result.connections
                self.appendix = result.appendix
                let now = Date()
                let futureCount = result.connections.filter { $0.firstDeparture > now }.count
                if result.connections.isEmpty {
                    self.error = "No direct flights found from \(origin.iata) to \(destination.iata) on this date. FlightAware only publishes schedules within ~48 hours of departure — try a date closer to today."
                } else if futureCount == 0 {
                    self.error = "All direct flights from \(origin.iata) to \(destination.iata) on this date have already departed."
                }
            } else {
                // Where-can-I-go mode → /departures, plus a follow-up call
                // for the next calendar day if the window crosses midnight.
                let windowEnd = referenceDate.addingTimeInterval(TimeInterval(windowHours * 3600))
                var allConnections: [RouteConnection] = []
                var capturedAppendix: RouteAppendix?

                let day1 = try await client.searchDepartures(
                    from: origin.iata,
                    date: referenceDate,
                    maxStops: 0,
                    limit: 200
                )
                allConnections.append(contentsOf: day1.connections)
                capturedAppendix = day1.appendix

                let cal = Calendar.current
                if !cal.isDate(referenceDate, inSameDayAs: windowEnd) {
                    let day2 = try await client.searchDepartures(
                        from: origin.iata,
                        date: windowEnd,
                        maxStops: 0,
                        limit: 200
                    )
                    allConnections.append(contentsOf: day2.connections)
                    if capturedAppendix == nil { capturedAppendix = day2.appendix }
                }

                self.connections = allConnections
                self.appendix = capturedAppendix
                if filteredFlights.isEmpty {
                    self.error = "Nothing leaving \(origin.iata) in the next \(windowHours)h."
                }
            }
        } catch RouteExplorerClient.ClientError.needsTokenRefresh {
            // Token expired or never captured. The setup screen lives
            // in Settings → Tools; tell the user how to refresh.
            isLoading = false
            self.error = "Route-explorer token expired. Open Settings → Tools → Connect route-explorer, then tap the bookmarklet in Safari to refresh."
            return
        } catch RouteExplorerClient.ClientError.needsClearance {
            // Legacy gate-clearance path — no longer reachable in
            // production (we removed the WKWebView fetch). Treat as
            // a token-refresh prompt for consistency.
            isLoading = false
            self.error = "Route-explorer needs a fresh token. Open Settings → Tools → Connect route-explorer."
            return
        } catch let err as FlightAwareScheduleClient.ClientError {
            self.error = err.errorDescription
        } catch {
            self.error = (error as? RouteExplorerClient.ClientError)?.errorDescription
                ?? error.localizedDescription
        }
        isLoading = false
    }

    // MARK: - Tap routing

    /// Tap a connection (multi-stop or direct) → present its full detail.
    private func openConnection(_ connection: RouteConnection) {
        pendingSheet = ConnectionLoadRequest(connection: connection, appendix: appendix)
    }

    /// Tap a single Where-can-I-go leg → wrap it in a one-flight connection
    /// so ConnectionLoadDetailView can render it the same way.
    private func openSingleLeg(_ leg: RouteFlight) {
        let single = RouteConnection(
            durationMinutes: leg.durationMinutes,
            score: 0,
            flights: [leg]
        )
        pendingSheet = ConnectionLoadRequest(connection: single, appendix: appendix)
    }
}

// MARK: - Departure leg row (Where-can-I-go mode results)

/// Compact card for a single departure in the Where-can-I-go results list.
/// IATA + airport name, time-of-day with a colored countdown, capacity pills.
/// Tapping opens the same `ConnectionLoadDetailView` as connection rows.
private struct DepartureLegRow: View {
    let leg: RouteFlight
    let appendix: RouteAppendix?
    let database: AirportDatabase
    let referenceDate: Date

    private static let timeFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateFormat = "HH:mm"
        return f
    }()

    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(alignment: .top, spacing: 10) {
                VStack(alignment: .leading, spacing: 2) {
                    // verbatim: prevents SwiftUI from running the Int through
                    // locale formatting and rendering "AA 6,380" with a comma.
                    Text(verbatim: "\(leg.carrierIata) \(leg.flightNumber)")
                        .font(.subheadline.weight(.bold))
                        .foregroundStyle(FlightTheme.textPrimary)
                    Text(airlineName)
                        .font(.caption)
                        .foregroundStyle(FlightTheme.textSecondary)
                        .lineLimit(1)
                        .truncationMode(.tail)
                }

                Spacer(minLength: 8)

                VStack(alignment: .trailing, spacing: 2) {
                    Text(Self.timeFormatter.string(from: leg.departure.dateTime))
                        .font(.subheadline.weight(.semibold).monospaced())
                        .foregroundStyle(FlightTheme.textPrimary)
                    Text(leavesIn)
                        .font(.caption2)
                        .foregroundStyle(leavesInColor)
                }
                .fixedSize(horizontal: true, vertical: false)
            }

            HStack(spacing: 12) {
                Text(leg.departure.airportIata)
                    .font(FlightTheme.airportCode(22))
                    .foregroundStyle(FlightTheme.textPrimary)
                Image(systemName: "airplane")
                    .font(.footnote)
                    .foregroundStyle(FlightTheme.textTertiary)
                    .rotationEffect(.degrees(-45))
                Text(leg.arrival.airportIata)
                    .font(FlightTheme.airportCode(22))
                    .foregroundStyle(FlightTheme.textPrimary)
                Spacer(minLength: 0)
            }

            HStack(spacing: 8) {
                Text("\(airportName(for: leg.departure.airportIata)) → \(airportName(for: leg.arrival.airportIata))")
                    .font(.caption)
                    .foregroundStyle(FlightTheme.textSecondary)
                    .lineLimit(1)
                    .truncationMode(.middle)
                Spacer(minLength: 8)
                if let aircraft = aircraftLabel {
                    Text(aircraft)
                        .font(FlightTheme.label(11))
                        .foregroundStyle(FlightTheme.textSecondary)
                        .lineLimit(1)
                        .truncationMode(.tail)
                        .padding(.horizontal, 8)
                        .padding(.vertical, 3)
                        .background(Color(.quaternarySystemFill), in: Capsule())
                }
            }

            HStack(spacing: 8) {
                if let total = leg.totalSeats {
                    metaPill("\(total) seats")
                }
                if let f = leg.classes?.first?.seats, f > 0 { metaPill("F·\(f)") }
                if let j = leg.classes?.business?.seats, j > 0 { metaPill("J·\(j)") }
                if let w = leg.classes?.premiumEconomy?.seats, w > 0 { metaPill("W·\(w)") }
                if let y = leg.classes?.economy?.seats, y > 0 { metaPill("Y·\(y)") }
                Spacer()
                Image(systemName: "chevron.right")
                    .font(.caption2)
                    .foregroundStyle(FlightTheme.textTertiary)
            }
        }
        .flightCard()
    }

    private func metaPill(_ text: String) -> some View {
        Text(text)
            .font(.caption2.monospaced())
            .foregroundStyle(FlightTheme.textSecondary)
            .padding(.horizontal, 8)
            .padding(.vertical, 3)
            .background(FlightTheme.accent.opacity(0.10), in: Capsule())
    }

    private var airlineName: String {
        appendix?.airline(iata: leg.carrierIata)?.name ?? leg.carrierIata
    }

    private var aircraftLabel: String? {
        guard let iata = leg.equipmentIata else { return nil }
        return appendix?.equipment(iata: iata)?.name ?? iata
    }

    /// Bundled DB first (clean city names), then route-explorer appendix.
    private func airportName(for iata: String) -> String {
        if let m = database.airport(byIATA: iata) { return m.name }
        if let n = appendix?.airport(iata: iata)?.cityName, !n.isEmpty { return n }
        if let n = appendix?.airport(iata: iata)?.name, !n.isEmpty { return n }
        return iata
    }

    private var leavesIn: String {
        let mins = Int(leg.departure.dateTime.timeIntervalSince(referenceDate) / 60)
        if mins < 0 { return "departed" }
        if mins < 60 { return "in \(mins)m" }
        let h = mins / 60
        let m = mins % 60
        if m == 0 { return "in \(h)h" }
        return "in \(h)h \(m)m"
    }

    private var leavesInColor: Color {
        let mins = Int(leg.departure.dateTime.timeIntervalSince(referenceDate) / 60)
        switch mins {
        case ..<30:   return FlightTheme.cancelled    // hurry
        case 30..<90: return FlightTheme.delayed      // soon
        default:      return FlightTheme.textSecondary
        }
    }
}
