import SwiftUI

/// Presents load data for ALL legs of a multi-stop connection at once.
///
/// Each leg's `AirlineLoadService.fetchLoad(...)` runs in parallel inside a
/// TaskGroup so the slowest carrier doesn't block the others — the user sees
/// the fastest leg's open/standby summary as soon as it lands. Per-leg
/// "Full details" buttons drill into the existing `FlightLoadDetailView`
/// for the upgrade/standby passenger lists.
struct ConnectionLoadDetailView: View {
    let connection: RouteConnection
    let appendix: RouteAppendix?
    let database: AirportDatabase
    let loadService: AirlineLoadService

    @Environment(\.dismiss) private var dismiss

    @State private var legStates: [LegLoadState]
    @State private var drillDown: RouteLoadDetailRequest?

    init(
        connection: RouteConnection,
        appendix: RouteAppendix?,
        database: AirportDatabase,
        loadService: AirlineLoadService
    ) {
        self.connection = connection
        self.appendix = appendix
        self.database = database
        self.loadService = loadService
        self._legStates = State(initialValue: connection.flights.map { LegLoadState(leg: $0) })
    }

    var body: some View {
        NavigationStack {
            ScrollView {
                VStack(alignment: .leading, spacing: FlightTheme.sectionSpacing) {
                    // Multi-leg only: stops + carriers + total duration. For
                    // a single-leg presentation (direct or Where-Can-I-Go),
                    // the leg card itself carries all the same info.
                    if connection.flights.count > 1 {
                        headerCard
                    }

                    ForEach(Array(legStates.enumerated()), id: \.element.id) { index, state in
                        if index > 0, let mins = layoverMinutes(at: index) {
                            layoverRow(minutes: mins, at: state.leg.departure.airportIata)
                        }
                        legCard(for: state)
                    }
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
            }
            .background(FlightTheme.background.ignoresSafeArea())
            .navigationTitle(navTitle)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button {
                        dismiss()
                    } label: {
                        Image(systemName: "xmark")
                            .font(.subheadline.weight(.semibold))
                            .foregroundStyle(FlightTheme.textSecondary)
                    }
                }
            }
            .task {
                await fetchAllLegs()
            }
            .sheet(item: $drillDown) { req in
                FlightLoadDetailView(
                    schedule: req.schedule,
                    departureCode: req.departureCode,
                    arrivalCode: req.arrivalCode,
                    date: req.date,
                    loadService: loadService
                )
            }
        }
    }

    // MARK: - Header card

    private var headerCard: some View {
        HStack(alignment: .top, spacing: 10) {
            VStack(alignment: .leading, spacing: 4) {
                Text(stopsLabel)
                    .font(.caption.weight(.semibold))
                    .foregroundStyle(FlightTheme.accent)
                    .padding(.horizontal, 8)
                    .padding(.vertical, 3)
                    .background(FlightTheme.accent.opacity(0.12), in: Capsule())
                Text(carriersLabel)
                    .font(.caption)
                    .foregroundStyle(FlightTheme.textSecondary)
                    .lineLimit(2)
            }

            Spacer(minLength: 8)

            VStack(alignment: .trailing, spacing: 2) {
                Text(formatDuration(connection.durationMinutes))
                    .font(.subheadline.weight(.bold))
                    .foregroundStyle(FlightTheme.textPrimary)
                Text("total")
                    .font(.caption2)
                    .foregroundStyle(FlightTheme.textTertiary)
            }
            .fixedSize(horizontal: true, vertical: false)
        }
        .flightCard()
    }

    // MARK: - Per-leg card

    private func legCard(for state: LegLoadState) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            // Flight header
            HStack(alignment: .top, spacing: 10) {
                VStack(alignment: .leading, spacing: 2) {
                    Text(verbatim: "\(state.leg.carrierIata) \(state.leg.flightNumber)")
                        .font(.subheadline.weight(.bold))
                        .foregroundStyle(FlightTheme.textPrimary)
                    Text(airlineName(for: state.leg))
                        .font(.caption)
                        .foregroundStyle(FlightTheme.textSecondary)
                        .lineLimit(1)
                }
                Spacer(minLength: 8)
                VStack(alignment: .trailing, spacing: 2) {
                    Text("\(timeFmt(state.leg.departure.dateTime)) → \(timeFmt(state.leg.arrival.dateTime))")
                        .font(.subheadline.weight(.semibold).monospaced())
                        .foregroundStyle(FlightTheme.textPrimary)
                    Text(formatDuration(state.leg.durationMinutes))
                        .font(.caption2)
                        .foregroundStyle(FlightTheme.textTertiary)
                }
                .fixedSize(horizontal: true, vertical: false)
            }

            // IATAs
            HStack(spacing: 12) {
                Text(state.leg.departure.airportIata)
                    .font(FlightTheme.airportCode(22))
                    .foregroundStyle(FlightTheme.textPrimary)
                Image(systemName: "airplane")
                    .font(.footnote)
                    .foregroundStyle(FlightTheme.textTertiary)
                    .rotationEffect(.degrees(-45))
                Text(state.leg.arrival.airportIata)
                    .font(FlightTheme.airportCode(22))
                    .foregroundStyle(FlightTheme.textPrimary)
                Spacer(minLength: 0)
                if let aircraft = aircraftLabel(for: state.leg) {
                    Text(aircraft)
                        .font(FlightTheme.label(11))
                        .foregroundStyle(FlightTheme.textSecondary)
                        .lineLimit(1)
                        .truncationMode(.tail)
                        .padding(.horizontal, 8)
                        .padding(.vertical, 3)
                        .background(Color(.quaternarySystemFill), in: Capsule())
                }
            }

            Text("\(airportName(for: state.leg.departure.airportIata)) → \(airportName(for: state.leg.arrival.airportIata))")
                .font(.caption)
                .foregroundStyle(FlightTheme.textSecondary)
                .lineLimit(1)
                .truncationMode(.middle)

            Divider()

            // Load content (loading / data / unavailable)
            loadContent(for: state)

            // Drill into full details
            Button {
                drillDown = RouteLoadDetailRequest(
                    schedule: state.leg.toFlightSchedule(appendix: appendix, on: state.leg.departure.dateTime),
                    departureCode: state.leg.departure.airportIata,
                    arrivalCode: state.leg.arrival.airportIata,
                    date: state.leg.departure.dateTime
                )
            } label: {
                HStack {
                    Text("Full details")
                        .font(.caption.weight(.semibold))
                    Spacer()
                    Image(systemName: "chevron.right").font(.caption2)
                }
                .foregroundStyle(FlightTheme.accent)
            }
            .buttonStyle(.plain)
        }
        .flightCard()
    }

    @ViewBuilder
    private func loadContent(for state: LegLoadState) -> some View {
        if state.isLoading {
            HStack(spacing: 8) {
                ProgressView().tint(FlightTheme.accent)
                Text("Loading load data…")
                    .font(.caption)
                    .foregroundStyle(FlightTheme.textSecondary)
                Spacer()
            }
            .frame(minHeight: 44)
        } else if let load = state.load {
            loadSummary(load)
        } else {
            HStack(spacing: 8) {
                Image(systemName: "info.circle")
                    .foregroundStyle(FlightTheme.textTertiary)
                Text("Load data isn't available for this flight.")
                    .font(.caption)
                    .foregroundStyle(FlightTheme.textSecondary)
                Spacer()
            }
            .frame(minHeight: 44)
        }
    }

    private func loadSummary(_ load: FlightLoad) -> some View {
        let openSeats: Int
        let standbyCount: Int
        if load.hasCabinData {
            openSeats = load.totalAvailable
            standbyCount = load.totalStandbyFromPBTS
        } else {
            openSeats = load.seatAvailability.reduce(0) { $0 + $1.available }
            standbyCount = load.standbyList.count
        }

        return VStack(alignment: .leading, spacing: 10) {
            HStack(spacing: 0) {
                VStack(spacing: 2) {
                    Text(verbatim: "\(openSeats)")
                        .font(.system(size: 28, weight: .bold, design: .rounded))
                        .foregroundStyle(FlightTheme.onTime)
                    Text("Open")
                        .font(FlightTheme.label())
                        .foregroundStyle(FlightTheme.textSecondary)
                }
                .frame(maxWidth: .infinity)

                Divider().frame(height: 36)

                VStack(spacing: 2) {
                    Text(verbatim: "\(standbyCount)")
                        .font(.system(size: 28, weight: .bold, design: .rounded))
                        .foregroundStyle(standbyCount > openSeats ? FlightTheme.cancelled : FlightTheme.delayed)
                    Text("Standby")
                        .font(FlightTheme.label())
                        .foregroundStyle(FlightTheme.textSecondary)
                }
                .frame(maxWidth: .infinity)
            }

            if !load.cabins.isEmpty {
                cabinPills(load.cabins)
            } else if !load.seatAvailability.isEmpty {
                seatPills(load.seatAvailability)
            }
        }
    }

    private func cabinPills(_ cabins: [CabinLoad]) -> some View {
        FlowLayoutHStack(spacing: 6) {
            ForEach(cabins) { cabin in
                pill("\(cabinShort(cabin.name)) \(cabin.available)/\(cabin.capacity)")
            }
        }
    }

    private func seatPills(_ items: [SeatAvailability]) -> some View {
        FlowLayoutHStack(spacing: 6) {
            ForEach(items) { item in
                pill("\(item.label): \(item.available)")
            }
        }
    }

    private func pill(_ 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())
    }

    // MARK: - Layover

    private func layoverMinutes(at index: Int) -> Int? {
        guard index >= 1, index < connection.flights.count else { return nil }
        let arr = connection.flights[index - 1].arrival.dateTime
        let dep = connection.flights[index].departure.dateTime
        let mins = Int(dep.timeIntervalSince(arr) / 60)
        return mins > 0 ? mins : nil
    }

    private func layoverRow(minutes: Int, at iata: String) -> some View {
        HStack(spacing: 8) {
            Image(systemName: "arrow.down")
                .font(.caption2)
                .foregroundStyle(FlightTheme.textTertiary)
            Text("Layover at \(iata) · \(formatDuration(minutes))")
                .font(.caption)
                .foregroundStyle(FlightTheme.textSecondary)
            Spacer()
        }
        .padding(.leading, 24)
    }

    // MARK: - Fetching

    private func fetchAllLegs() async {
        await withTaskGroup(of: (Int, FlightLoad?).self) { group in
            for (i, leg) in connection.flights.enumerated() {
                let airlineCode = leg.carrierIata
                let flightNumber = "\(leg.flightNumber)"
                let date = leg.departure.dateTime
                let origin = leg.departure.airportIata
                let destination = leg.arrival.airportIata
                let depTime = Self.timeFormatter.string(from: leg.departure.dateTime)

                group.addTask { [loadService] in
                    let load = await loadService.fetchLoad(
                        airlineCode: airlineCode,
                        flightNumber: flightNumber,
                        date: date,
                        origin: origin,
                        destination: destination,
                        departureTime: depTime
                    )
                    return (i, load)
                }
            }

            for await (i, load) in group {
                guard i < legStates.count else { continue }
                legStates[i].load = load
                legStates[i].isLoading = false
            }
        }
    }

    // MARK: - Helpers

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

    private func timeFmt(_ d: Date) -> String { Self.timeFormatter.string(from: d) }

    private var stopsLabel: String {
        switch connection.stopCount {
        case 0:  return "Direct"
        case 1:  return "1-stop Connection"
        default: return "\(connection.stopCount)-stop Connection"
        }
    }

    /// Nav-bar title. Single legs get the route ("DFW → SHV"); multi-stops
    /// get the stops label so the user can tell at a glance.
    private var navTitle: String {
        if connection.flights.count == 1, let leg = connection.flights.first {
            return "\(leg.departure.airportIata) → \(leg.arrival.airportIata)"
        }
        return stopsLabel
    }

    private var carriersLabel: String {
        let codes = connection.carrierIatas
        if codes.count == 1, let app = appendix?.airline(iata: codes[0])?.name {
            return app
        }
        let names = codes.map { appendix?.airline(iata: $0)?.name ?? $0 }
        return names.joined(separator: " · ")
    }

    private func airlineName(for leg: RouteFlight) -> String {
        appendix?.airline(iata: leg.carrierIata)?.name ?? leg.carrierIata
    }

    private func aircraftLabel(for leg: RouteFlight) -> 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
    }

    /// Map a cabin name to a short fare-class letter for compact pills.
    private func cabinShort(_ name: String) -> String {
        let lower = name.lowercased()
        if lower.contains("first") { return "F" }
        if lower.contains("polaris") || lower.contains("business") { return "J" }
        if lower.contains("premium") { return "W" }
        if lower.contains("economy") || lower.contains("main") || lower.contains("rear") { return "Y" }
        if lower.contains("front") { return "F" }
        if lower.contains("middle") { return "J" }
        return String(name.prefix(3)).uppercased()
    }

    private func formatDuration(_ minutes: Int) -> String {
        let h = minutes / 60
        let m = minutes % 60
        if h > 0, m > 0 { return "\(h)h \(m)m" }
        if h > 0        { return "\(h)h" }
        return "\(m)m"
    }
}

// MARK: - Per-leg load state

private struct LegLoadState: Identifiable {
    let id: String
    let leg: RouteFlight
    var load: FlightLoad?
    var isLoading: Bool

    init(leg: RouteFlight) {
        self.id = leg.id
        self.leg = leg
        self.load = nil
        self.isLoading = true
    }
}

// MARK: - Wrapping HStack for pills

/// Lightweight wrapping HStack so cabin pills flow onto multiple lines on
/// narrow widths instead of clipping or pushing past the card edge.
private struct FlowLayoutHStack<Content: View>: View {
    let spacing: CGFloat
    @ViewBuilder var content: () -> Content

    init(spacing: CGFloat = 6, @ViewBuilder content: @escaping () -> Content) {
        self.spacing = spacing
        self.content = content
    }

    var body: some View {
        // Use SwiftUI's iOS 16+ Layout via `ViewThatFits` over single-line and
        // multi-line variants. For the small pill counts we have, a simple
        // horizontal stack with wrapping is enough; if the pill row overflows
        // we fall back to stacking each pill on its own row.
        ViewThatFits(in: .horizontal) {
            HStack(spacing: spacing) {
                content()
                Spacer(minLength: 0)
            }
            VStack(alignment: .leading, spacing: spacing) {
                content()
            }
        }
    }
}
