import SwiftUI
import UIKit
import WebKit

/// Settings → Tools → Diagnostics. Surfaces every log file
/// ``DiagnosticLogger`` has written this install, lets the user
/// preview them inline, and exports any one of them through the iOS
/// share sheet (AirDrop / mail / Files / iMessage) — the path by
/// which a user on a real device can ship a forensic dump to us when
/// something fails in a way we can't reproduce in the simulator.
///
/// Buttons:
///   • Run gate scenario — opens an off-screen WKWebView at
///     route-explorer.com, polls /api/token every 1.5s for 30s,
///     captures cookies + status on every tick. This is the
///     "Turnstile won't pass" debug trace.
///   • Run search scenario — fires both the route-explorer search
///     path (with gate-clearance dependency) AND the FlightAware path
///     so the log shows both transports side-by-side for the same
///     route+date.
///   • Tap a row to share — uses ``UIActivityViewController`` so the
///     user gets the standard share sheet.
struct DiagnosticsView: View {
    @State private var logFiles: [URL] = []
    @State private var loggerEnabled: Bool = true
    @State private var shareURL: URL?
    @State private var scenarioRunning: String?

    var body: some View {
        List {
            controlsSection
            scenariosSection
            logsSection
        }
        .navigationTitle("Diagnostics")
        .navigationBarTitleDisplayMode(.inline)
        .onAppear { refresh() }
        .sheet(item: $shareURL) { url in
            ShareSheet(items: [url])
        }
    }

    // MARK: - Sections

    private var controlsSection: some View {
        Section {
            Toggle("Logging enabled", isOn: $loggerEnabled)
                .onChange(of: loggerEnabled) { _, on in
                    DiagnosticLogger.shared.setEnabled(on)
                }
            HStack {
                Text("Session ID").foregroundStyle(.secondary)
                Spacer()
                Text(DiagnosticLogger.shared.sessionID)
                    .font(.footnote.monospaced())
            }
            HStack {
                Text("Current log file").foregroundStyle(.secondary)
                Spacer()
                if let url = DiagnosticLogger.shared.logFileURL {
                    Text(url.lastPathComponent)
                        .font(.caption2.monospaced())
                        .lineLimit(1)
                        .truncationMode(.middle)
                } else {
                    Text("(none)").foregroundStyle(.secondary)
                }
            }
            Button("Clear all log files") {
                DiagnosticLogger.shared.clearAll()
                refresh()
            }
        } header: {
            Text("Controls")
        } footer: {
            Text("Logs are tab-separated text. Each line is one event with timestamp, category, and key=value fields. Files live under the app's Documents/Diagnostics/.")
        }
    }

    private var scenariosSection: some View {
        Section {
            Button {
                Task { await runGateScenario() }
            } label: {
                scenarioRow(title: "Run gate scenario (30s)",
                            subtitle: "Polls route-explorer /api/token; captures cookies + JS console + final status",
                            symbol: "shield.lefthalf.filled",
                            running: scenarioRunning == "gate")
            }
            .disabled(scenarioRunning != nil)

            Button {
                Task { await runFlightAwareScenario() }
            } label: {
                scenarioRow(title: "Run FlightAware scenario",
                            subtitle: "DFW→AMS direct; captures route.rvt + trackpoll request shapes",
                            symbol: "airplane",
                            running: scenarioRunning == "fa")
            }
            .disabled(scenarioRunning != nil)
        } header: {
            Text("Scenarios")
        } footer: {
            Text("Tap a scenario to run a fixed trace. Result lands in the current log file; share it from the list below.")
        }
    }

    private var logsSection: some View {
        Section {
            if logFiles.isEmpty {
                Text("No log files").foregroundStyle(.secondary)
            } else {
                ForEach(logFiles, id: \.self) { url in
                    Button {
                        shareURL = url
                    } label: {
                        logRow(url: url)
                    }
                }
            }
        } header: {
            HStack {
                Text("Log files")
                Spacer()
                Button("Refresh") { refresh() }
                    .font(.caption)
            }
        } footer: {
            Text("Tap a file to share via AirDrop, email, or iMessage. Open files with a text app to view.")
        }
    }

    // MARK: - Row builders

    private func scenarioRow(title: String, subtitle: String, symbol: String, running: Bool) -> some View {
        HStack(alignment: .top, spacing: 12) {
            Image(systemName: symbol)
                .foregroundStyle(.tint)
                .frame(width: 28)
            VStack(alignment: .leading, spacing: 2) {
                Text(title).font(.footnote.weight(.semibold))
                Text(subtitle).font(.caption2).foregroundStyle(.secondary)
            }
            Spacer()
            if running {
                ProgressView()
            }
        }
        .contentShape(Rectangle())
    }

    private func logRow(url: URL) -> some View {
        let attrs = (try? FileManager.default.attributesOfItem(atPath: url.path)) ?? [:]
        let size = (attrs[.size] as? Int) ?? 0
        let date = (attrs[.modificationDate] as? Date) ?? Date()
        let sizeStr = ByteCountFormatter().string(fromByteCount: Int64(size))
        let dateStr = Self.dateFormatter.string(from: date)
        let isCurrent = url == DiagnosticLogger.shared.logFileURL
        return HStack {
            VStack(alignment: .leading, spacing: 2) {
                HStack(spacing: 6) {
                    Text(url.lastPathComponent)
                        .font(.footnote.monospaced())
                    if isCurrent {
                        Text("CURRENT").font(.caption2.weight(.heavy)).foregroundStyle(.green)
                    }
                }
                Text("\(dateStr)  ·  \(sizeStr)").font(.caption2).foregroundStyle(.secondary)
            }
            Spacer()
            Image(systemName: "square.and.arrow.up").foregroundStyle(.tint)
        }
    }

    private static let dateFormatter: DateFormatter = {
        let f = DateFormatter()
        f.dateStyle = .short
        f.timeStyle = .medium
        return f
    }()

    // MARK: - Actions

    private func refresh() {
        logFiles = DiagnosticLogger.shared.allLogFiles()
    }

    private func runGateScenario() async {
        scenarioRunning = "gate"
        defer { scenarioRunning = nil; refresh() }
        DiagnosticLogger.shared.log("SCEN", "gateBegin", [:])
        await GateScenarioRunner.run(durationSeconds: 30)
        DiagnosticLogger.shared.log("SCEN", "gateEnd", [:])
    }

    private func runFlightAwareScenario() async {
        scenarioRunning = "fa"
        defer { scenarioRunning = nil; refresh() }
        DiagnosticLogger.shared.log("SCEN", "faBegin", ["route": "DFW->AMS"])
        let client = FlightAwareScheduleClient(database: AirportDatabase())
        let today = Date()
        do {
            let result = try await client.searchDirectFlights(from: "DFW", to: "AMS", date: today)
            DiagnosticLogger.shared.log("SCEN", "faResult", [
                "connections": result.connections.count,
            ])
        } catch {
            DiagnosticLogger.shared.log("SCEN", "faError", [
                "error": error.localizedDescription,
            ])
        }
        DiagnosticLogger.shared.log("SCEN", "faEnd", [:])
    }
}

// MARK: - Gate scenario runner

/// Encapsulates the off-screen WKWebView poll loop used by the
/// "Run gate scenario" button. Lives outside the View so it survives
/// even if the view is dismissed mid-run (no SwiftUI state binding).
@MainActor
private enum GateScenarioRunner {
    static func run(durationSeconds: Int) async {
        let config = WKWebViewConfiguration()
        config.websiteDataStore = .default()
        let contentController = WKUserContentController()
        // Bridge console messages into the logger.
        let bridge = """
        (function() {
            const orig = window.console;
            const send = (lvl, args) => {
                try {
                    window.webkit.messageHandlers.diag.postMessage(
                        lvl + ": " + Array.from(args).map(a =>
                            (typeof a === 'object' ? JSON.stringify(a) : String(a))
                        ).join(' ').substring(0, 240)
                    );
                } catch (e) {}
            };
            ['log','info','warn','error','debug'].forEach(lvl => {
                const f = orig[lvl];
                orig[lvl] = function(...args) { send(lvl, args); return f.apply(orig, args); };
            });
        })();
        """
        contentController.addUserScript(WKUserScript(
            source: bridge, injectionTime: .atDocumentStart, forMainFrameOnly: false
        ))
        let handler = ScenarioConsoleHandler()
        contentController.add(handler, name: "diag")
        config.userContentController = contentController

        let webView = WKWebView(frame: .zero, configuration: config)
        webView.customUserAgent =
            "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) "
            + "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 "
            + "Mobile/15E148 Safari/604.1"
        let delegate = ScenarioNavigationDelegate()
        webView.navigationDelegate = delegate

        // Load homepage.
        webView.load(URLRequest(url: URL(string: "https://route-explorer.com/")!))
        DiagnosticLogger.shared.log("SCEN", "gateLoaded", [
            "url": "https://route-explorer.com/",
        ])

        // Wait for first navigation to finish (best-effort).
        try? await Task.sleep(nanoseconds: 1_500_000_000)

        // Poll loop.
        let deadline = Date().addingTimeInterval(TimeInterval(durationSeconds))
        var tick = 0
        while Date() < deadline {
            tick += 1
            await probe(webView: webView, tick: tick)
            try? await Task.sleep(nanoseconds: 1_500_000_000)
        }
        // Snapshot final cookies.
        let cookies = await WKWebsiteDataStore.default().httpCookieStore.allCookies()
        let reCookies = cookies.filter { $0.domain.contains("route-explorer.com") }
        DiagnosticLogger.shared.log("SCEN", "gateFinal", [
            "ticks": tick,
            "cookieCount": reCookies.count,
            "names": reCookies.map { $0.name }.sorted().joined(separator: ","),
            "hasRexClearance": reCookies.contains { $0.name == "rex_clearance" },
        ])
    }

    private static func probe(webView: WKWebView, tick: Int) async {
        let js = """
        return await new Promise((res) => {
            fetch('/api/token', { credentials: 'include' })
                .then(r => r.text().then(t => res({status: r.status, body: t})))
                .catch(e => res({status: -1, body: String(e)}));
        });
        """
        let raw = try? await webView.callAsyncJavaScript(js, contentWorld: .page)
        let dict = raw as? [String: Any]
        let status = dict?["status"] as? Int ?? -1
        let body = dict?["body"] as? String ?? ""
        DiagnosticLogger.shared.log("SCEN", "gateProbe", [
            "tick": tick,
            "status": status,
            "body": String(body.prefix(200)),
        ])
    }
}

@MainActor
private final class ScenarioConsoleHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let body = message.body as? String else { return }
        DiagnosticLogger.shared.log("SCEN", "gateConsole", ["msg": body])
    }
}

@MainActor
private final class ScenarioNavigationDelegate: NSObject, WKNavigationDelegate {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        DiagnosticLogger.shared.log("SCEN", "gateNavDone", [
            "url": webView.url?.absoluteString ?? "?",
        ])
    }
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        DiagnosticLogger.shared.log("SCEN", "gateNavFailed", [
            "error": error.localizedDescription,
        ])
    }
    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        DiagnosticLogger.shared.log("SCEN", "gateNavFailedProvisional", [
            "error": error.localizedDescription,
        ])
    }
    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse) async -> WKNavigationResponsePolicy {
        if let http = navigationResponse.response as? HTTPURLResponse {
            DiagnosticLogger.shared.log("SCEN", "gateNavResponse", [
                "url": http.url?.absoluteString ?? "?",
                "status": http.statusCode,
                "setCookie": String((http.value(forHTTPHeaderField: "Set-Cookie") ?? "").prefix(200)),
                "cfRay": http.value(forHTTPHeaderField: "CF-Ray") ?? "-",
                "server": http.value(forHTTPHeaderField: "Server") ?? "-",
            ])
        }
        return .allow
    }
}

// MARK: - Share sheet

struct ShareSheet: UIViewControllerRepresentable {
    let items: [Any]
    func makeUIViewController(context: Context) -> UIActivityViewController {
        UIActivityViewController(activityItems: items, applicationActivities: nil)
    }
    func updateUIViewController(_ vc: UIActivityViewController, context: Context) {}
}

extension URL: @retroactive Identifiable {
    public var id: String { absoluteString }
}
