import BitLogger
import Foundation
#if canImport(Network)
import Network
#endif
#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#endif

#if !canImport(Network)
private final class NWPathMonitor {
    var pathUpdateHandler: ((Any) -> Void)?

    func start(queue: DispatchQueue) {
        // Path monitoring is unavailable on this platform; nothing to do.
    }
}
#endif

// Preferred: tiny C glue that uses Tor's embedding API (tor_api.h)
@_silgen_name("tor_host_start")
private func tor_host_start(_ dataDir: UnsafePointer<CChar>, _ socksAddr: UnsafePointer<CChar>, _ controlAddr: UnsafePointer<CChar>, _ shutdownFast: Int32) -> Int32
@_silgen_name("tor_host_shutdown")
private func tor_host_shutdown() -> Int32
@_silgen_name("tor_host_restart")
private func tor_host_restart(_ dataDir: UnsafePointer<CChar>, _ socksAddr: UnsafePointer<CChar>, _ controlAddr: UnsafePointer<CChar>, _ shutdownFast: Int32) -> Int32
@_silgen_name("tor_host_is_running")
private func tor_host_is_running() -> Int32

/// Minimal Tor integration scaffold.
/// - Boots a local Tor client (once integrated) and exposes a SOCKS5 proxy
///   on 127.0.0.1:socksPort. All app networking should await readiness and
///   route via this proxy. Fails closed by default when Tor is unavailable.
/// - Drop-in ready: add your Tor framework and complete `startTor()`.
@MainActor
public final class TorManager: ObservableObject {
    public static let shared = TorManager()

    // SOCKS endpoint where the embedded Tor should listen.
    let socksHost: String = "127.0.0.1"
    let socksPort: Int = 39050

    // Optional ControlPort for debugging/diagnostics once Tor is integrated.
    let controlHost: String = "127.0.0.1"
    let controlPort: Int = 39051

    // State
    // True only when SOCKS is reachable AND bootstrap has reached 100%.
    @Published private(set) public var isReady: Bool = false
    @Published private(set) var isStarting: Bool = false
    @Published private(set) var lastError: Error?
    @Published private(set) var bootstrapProgress: Int = 0
    @Published private(set) var bootstrapSummary: String = ""
    
    // Internal readiness trackers
    private var socksReady: Bool = false { didSet { recomputeReady() } }
    private var restarting: Bool = false

    // Whether the app must enforce Tor for all connections (fail-closed).
    // This is the default. For local development, you may compile with
    // `-DBITCHAT_DEV_ALLOW_CLEARNET` to temporarily allow direct network.
    public var torEnforced: Bool {
        #if BITCHAT_DEV_ALLOW_CLEARNET
        return false
        #else
        return true
        #endif
    }

    // Returns true only when Tor is actually up (or dev fallback is compiled).
    var networkPermitted: Bool {
        if torEnforced { return isReady }
        // Dev bypass allows network even if Tor is not running
        return true
    }

    private var didStart = false
    private var controlMonitorStarted = false
    private var pathMonitor: NWPathMonitor?
    private var isAppForeground: Bool = true
    private var isDormant: Bool = false
    private var lastRestartAt: Date? = nil
    // Global policy gate: only allow Tor to start when true
    private(set) var allowAutoStart: Bool = false

    private init() {}

    // MARK: - Public API

    public func startIfNeeded() {
        // Respect global start policy
        guard allowAutoStart else { return }
        // Do not start in background; caller should wait for foreground
        guard isAppForeground else { return }
        guard !didStart else { return }
        didStart = true
        isDormant = false
        isStarting = true
        lastError = nil
        // Announce initial start so UI can show a status message
        NotificationCenter.default.post(name: .TorWillStart, object: nil)
        ensureFilesystemLayout()
        startTor()
        startPathMonitorIfNeeded()
    }

    /// Called by app lifecycle to indicate foreground/background state.
    public func setAppForeground(_ foreground: Bool) {
        isAppForeground = foreground
    }

    /// Foreground state accessor for other @MainActor clients.
    public func isForeground() -> Bool { isAppForeground }

    /// Await Tor bootstrap to readiness. Returns true if network is permitted (Tor ready or dev bypass).
    /// Nonisolated to avoid blocking the main actor during waits.
    nonisolated
    public func awaitReady(timeout: TimeInterval = 25.0) async -> Bool {
        // Only start Tor if we're in foreground; otherwise just wait for it
        await MainActor.run {
            if self.isAppForeground { self.startIfNeeded() }
        }
        let deadline = Date().addingTimeInterval(timeout)
        // Early exit if network already permitted
        if await MainActor.run(body: { self.networkPermitted }) { return true }
        while Date() < deadline {
            try? await Task.sleep(nanoseconds: 200_000_000) // 200ms
            if await MainActor.run(body: { self.networkPermitted }) { return true }
        }
        return await MainActor.run(body: { self.networkPermitted })
    }

    // MARK: - Filesystem (torrc + data dir)

    func dataDirectoryURL() -> URL? {
        do {
            let base = try FileManager.default.url(
                for: .applicationSupportDirectory,
                in: .userDomainMask,
                appropriateFor: nil,
                create: true
            )
            let dir = base.appendingPathComponent("bitchat/tor", isDirectory: true)
            return dir
        } catch {
            return nil
        }
    }

    func torrcURL() -> URL? {
        dataDirectoryURL()?.appendingPathComponent("torrc")
    }

    private func ensureFilesystemLayout() {
        guard let dir = dataDirectoryURL() else { return }
        do {
            try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
            // Always (re)write torrc at launch so DataDirectory is correct for this container
            if let torrc = torrcURL() {
                try torrcTemplate().data(using: .utf8)?.write(to: torrc, options: .atomic)
            }
        } catch {
            // Non-fatal; Tor will surface errors during start if paths are missing
        }
    }

    /// Minimal, safe torrc for an embedded client.
    func torrcTemplate() -> String {
        var lines: [String] = []
        if let dir = dataDirectoryURL()?.path {
            lines.append("DataDirectory \(dir)")
        }
        lines.append("ClientOnly 1")
        lines.append("SOCKSPort \(socksHost):\(socksPort)")
        lines.append("ControlPort \(controlHost):\(controlPort)")
        lines.append("CookieAuthentication 1")
        lines.append("AvoidDiskWrites 1")
        lines.append("MaxClientCircuitsPending 8")
        lines.append("ShutdownWaitLength 0")
        // Keep defaults for guard/exit selection to preserve anonymity properties
        return lines.joined(separator: "\n") + "\n"
    }

    // MARK: - Integration Hook

    /// Start the embedded Tor. This stub intentionally compiles without any Tor dependency.
    /// Integrate your Tor framework here and set `isReady = true` once bootstrapped.
    private func startTor() {
        // Prefer the embedding API wrapper which cleanly supports restart.
        // Avoid fallback to prevent accidental second instances in-process.
        if startTorViaEmbedAPI() { return }

        #if BITCHAT_DEV_ALLOW_CLEARNET
        // Dev bypass: permit network immediately (no Tor). Use ONLY for local development.
        self.isReady = true
        self.isStarting = false
        #else
        // Production default: fail closed until Tor framework is dropped in and bootstraps.
        self.isReady = false
        self.isStarting = false
        #endif
    }

    // MARK: - Embed API path (owning controller FD + clean restart)
    private func startTorViaEmbedAPI() -> Bool {
        guard let dir = dataDirectoryURL()?.path else { return false }
        let socks = "\(socksHost):\(socksPort)"
        let control = "\(controlHost):\(controlPort)"
        var started = false
        // If already running (per C glue), treat as started
        if tor_host_is_running() != 0 {
            SecureLogger.info("TorManager: embed reports already running", category: .session)
            return true
        }
        dir.withCString { dptr in
            socks.withCString { sptr in
                control.withCString { cptr in
                    let rc = tor_host_start(dptr, sptr, cptr, 1)
                    started = (rc == 0)
                    if rc != 0 {
                        SecureLogger.error("TorManager: tor_host_start failed rc=\(rc)", category: .session)
                    } else {
                        SecureLogger.info("TorManager: tor_host_start OK (\(socks), control \(control))", category: .session)
                    }
                }
            }
        }
        if !started { return false }

        // Start monitors and probe readiness
        startControlMonitorIfNeeded()
        Task.detached(priority: .userInitiated) { [weak self] in
            guard let self else { return }
            let ready = await self.waitForSocksReady(timeout: 60.0)
            await MainActor.run {
                self.socksReady = ready
                if ready {
                    SecureLogger.info("TorManager: SOCKS ready at \(self.socksHost):\(self.socksPort) [embed]", category: .session)
                } else {
                    self.lastError = NSError(domain: "TorManager", code: -14, userInfo: [NSLocalizedDescriptionKey: "Tor SOCKS not reachable after embed start"])
                    SecureLogger.error("TorManager: SOCKS not reachable (timeout) [embed]", category: .session)
                }
            }
        }
        return true
    }
    /// Probe the local SOCKS port until it's ready or a timeout elapses.
    private func waitForSocksReady(timeout: TimeInterval) async -> Bool {
        let deadline = Date().addingTimeInterval(timeout)
        while Date() < deadline {
            if await probeSocksOnce() { return true }
            try? await Task.sleep(nanoseconds: 250_000_000)
        }
        return false
    }

    private func probeSocksOnce() async -> Bool {
        await withCheckedContinuation { cont in
            let params = NWParameters.tcp
            let host = NWEndpoint.Host.ipv4(.loopback)
            guard let port = NWEndpoint.Port(rawValue: UInt16(socksPort)) else {
                cont.resume(returning: false)
                return
            }
            let endpoint = NWEndpoint.hostPort(host: host, port: port)
            let conn = NWConnection(to: endpoint, using: params)

            var resumed = false
            let resumeOnce: (Bool) -> Void = { value in
                if !resumed {
                    resumed = true
                    cont.resume(returning: value)
                }
            }

            conn.stateUpdateHandler = { state in
                switch state {
                case .ready:
                    resumeOnce(true)
                    conn.cancel()
                case .failed, .cancelled:
                    resumeOnce(false)
                    conn.cancel()
                default:
                    break
                }
            }

            // Failsafe timeout to avoid hanging if no callback occurs
            DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 1.0) {
                resumeOnce(false)
                conn.cancel()
            }

            conn.start(queue: DispatchQueue.global(qos: .utility))
        }
    }

    // MARK: - ControlPort monitoring (bootstrap progress)
    private func startControlMonitorIfNeeded() {
        guard !controlMonitorStarted else { return }
        controlMonitorStarted = true
        // Use a simple GETINFO poll on all platforms to avoid long-lived blocking streams
        Task.detached(priority: .utility) { [weak self] in
            await self?.bootstrapPollLoop()
        }
    }

    // iOS: Poll GETINFO periodically to track bootstrap progress without long-lived control readers.
    private func bootstrapPollLoop() async {
        let deadline = Date().addingTimeInterval(75)
        while Date() < deadline {
            if let info = await controlGetBootstrapInfo() {
                await MainActor.run {
                    self.bootstrapProgress = info.progress
                    self.bootstrapSummary = info.summary
                    if info.progress >= 100 { self.isStarting = false }
                    self.recomputeReady()
                }
                if info.progress >= 100 { break }
            }
            try? await Task.sleep(nanoseconds: 1_000_000_000)
        }
    }

    private func controlGetBootstrapInfo() async -> (progress: Int, summary: String)? {
        guard let text = await controlExchange(lines: ["GETINFO status/bootstrap-phase"], timeout: 2.0) else { return nil }
        var progress = self.bootstrapProgress
        var summary = self.bootstrapSummary
        // Search entire response for PROGRESS and SUMMARY tokens
        // Typical: "250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=75 TAG=... SUMMARY=\"...\"\r\n250 OK\r\n"
        let tokens = text.replacingOccurrences(of: "\r", with: " ").replacingOccurrences(of: "\n", with: " ").split(separator: " ")
        for t in tokens {
            if t.hasPrefix("PROGRESS=") {
                progress = Int(t.split(separator: "=").last ?? "0") ?? progress
            } else if t.hasPrefix("SUMMARY=") {
                let raw = String(t.dropFirst("SUMMARY=".count))
                summary = raw.trimmingCharacters(in: CharacterSet(charactersIn: "\""))
            }
        }
        return (progress, summary)
    }

    // MARK: - Foreground recovery and control helpers

    public func ensureRunningOnForeground() {
        // Respect global start policy
        if !allowAutoStart { return }
        // iOS can suspend Tor harshly; the most reliable approach for
        // embedding is to restart Tor every time we become active.
        Task.detached(priority: .userInitiated) { [weak self] in
            guard let self = self else { return }
            // Claim the restart under MainActor to avoid races
            let claimed: Bool = await MainActor.run {
                if self.isStarting || self.restarting { return false }
                self.restarting = true
                return true
            }
            if !claimed { return }
            if await self.resumeTorIfPossible() {
                await MainActor.run {
                    self.restarting = false
                    self.isStarting = false
                }
                return
            }
            await self.restartTor()
            await MainActor.run { self.restarting = false }
        }
    }

    public func goDormantOnBackground() {
        // Prefer Tor's DORMANT mode so we can resume on foreground without a full restart.
        // If the control port is unreachable, fall back to a hard shutdown.
        Task.detached { [weak self] in
            guard let self = self else { return }
            let signaled = await self.controlSendSignal("DORMANT")
            if signaled {
                SecureLogger.info("TorManager: signalled DORMANT", category: .session)
                await MainActor.run {
                    self.isDormant = true
                    self.isReady = false
                    self.socksReady = false
                    self.isStarting = false
                }
                return
            }

            SecureLogger.warning("TorManager: DORMANT signal failed; shutting down", category: .session)
            _ = tor_host_shutdown()
            await MainActor.run {
                self.isDormant = false
                self.isReady = false
                self.socksReady = false
                self.bootstrapProgress = 0
                self.bootstrapSummary = ""
                self.isStarting = false
                self.didStart = false
                // Allow control monitor to start anew on next start
                self.controlMonitorStarted = false
            }
        }
    }

    public func shutdownCompletely() {
        Task.detached { [weak self] in
            guard let self = self else { return }
            _ = tor_host_shutdown()
            await MainActor.run {
                self.isDormant = false
                self.isReady = false
                self.socksReady = false
                self.bootstrapProgress = 0
                self.bootstrapSummary = ""
                self.isStarting = false
                self.didStart = false
                self.restarting = false
                self.controlMonitorStarted = false
            }
        }
    }

    private func restartTor() async {
        await MainActor.run {
            // Announce restart so UI can notify the user
            NotificationCenter.default.post(name: .TorWillRestart, object: nil)
            self.isReady = false
            self.socksReady = false
            self.bootstrapProgress = 0
            self.bootstrapSummary = ""
            self.isStarting = true
            self.isDormant = false
            self.lastRestartAt = Date()
        }
        // Prefer clean shutdown via owning controller FD; join the tor thread
        _ = tor_host_shutdown()
        // As a fallback, try control signal if needed (harmless if tor already down)
        _ = await controlSendSignal("SHUTDOWN")
        // Allow Tor thread to fully terminate before re-starting.
        var waited = 0
        while tor_host_is_running() != 0 && waited < 40 {
            try? await Task.sleep(nanoseconds: 100_000_000) // 100ms
            waited += 1
        }
        if waited >= 40 {
            SecureLogger.warning("TorManager: tor_host_is_running still true before restart", category: .session)
        }
        // Allow control monitor and start logic to reinitialize cleanly
        await MainActor.run {
            self.controlMonitorStarted = false
            self.didStart = false
        }
        // Now start fresh
        await MainActor.run { self.startIfNeeded() }
    }

    private func recomputeReady() {
        let ready = socksReady && bootstrapProgress >= 100
        if ready != isReady {
            isReady = ready
            if ready {
                // Broadcast readiness so clients can rebuild sessions and reconnect
                NotificationCenter.default.post(name: .TorDidBecomeReady, object: nil)
            }
        }
    }

    private func startPathMonitorIfNeeded() {
        guard pathMonitor == nil else { return }
        let monitor = NWPathMonitor()
        pathMonitor = monitor
        let queue = DispatchQueue(label: "TorPathMonitor")
        monitor.pathUpdateHandler = { [weak self] _ in
            // On any path change, poke Tor/recover (hop to main actor).
            Task { @MainActor in
                guard let self = self else { return }
                // Avoid waking Tor while app is backgrounded; we'll handle on .active
                if self.isAppForeground {
                    self.pokeTorOnPathChange()
                }
            }
        }
        monitor.start(queue: queue)
    }

    private func pokeTorOnPathChange() {
        // If a restart just happened, avoid thrashing
        if let last = lastRestartAt, Date().timeIntervalSince(last) < 3.0 {
            return
        }
        // If we're starting or restarting, do nothing and let that complete
        if isStarting || restarting { return }
        Task.detached(priority: .userInitiated) {
            let ok = await self.controlPingBootstrap()
            if ok {
                _ = await self.controlSendSignal("ACTIVE")
                return
            }
            // If bootstrapping is underway, let it finish
            let stillBootstrapping = await MainActor.run { self.socksReady && self.bootstrapProgress < 100 }
            if stillBootstrapping { return }
            // As a last resort, restart
            await self.ensureRunningOnForeground()
        }
    }

    // Lightweight control: authenticate and GETINFO bootstrap-phase.
    private func controlPingBootstrap(timeout: TimeInterval = 3.0) async -> Bool {
        let data = await controlExchange(lines: ["GETINFO status/bootstrap-phase"], timeout: timeout)
        guard let text = data else { return false }
        return text.contains("status/bootstrap-phase")
    }

    private func controlSendSignal(_ signal: String, timeout: TimeInterval = 3.0) async -> Bool {
        let text = await controlExchange(lines: ["SIGNAL \(signal)"], timeout: timeout)
        return (text?.contains("250")) == true
    }

    private func resumeTorIfPossible() async -> Bool {
        let wasDormant = await MainActor.run { self.isDormant }
        let pendingReady = await MainActor.run { self.socksReady && !self.isReady }
        let needsWake = wasDormant || pendingReady
        if !needsWake {
            return false
        }

        let activated = await controlSendSignal("ACTIVE")
        let pinged = await controlPingBootstrap(timeout: 3.0)
        if !activated && !pinged {
            SecureLogger.warning("TorManager: ACTIVE signal failed", category: .session)
            return false
        }

        if let info = await controlGetBootstrapInfo() {
            await MainActor.run {
                self.bootstrapProgress = info.progress
                self.bootstrapSummary = info.summary
            }
        }

        await MainActor.run {
            self.isDormant = false
            self.isStarting = true
            self.socksReady = false
        }

        let firstReady = await waitForSocksReady(timeout: 12.0)
        if firstReady {
            await MainActor.run {
                self.socksReady = true
                self.isStarting = false
            }
            SecureLogger.info("TorManager: resumed Tor via ACTIVE signal", category: .session)
            return true
        }

        if pinged {
            let secondReady = await waitForSocksReady(timeout: 20.0)
            await MainActor.run {
                self.socksReady = secondReady
                self.isStarting = !secondReady
            }
            if secondReady {
                SecureLogger.info("TorManager: resumed Tor after extended wait", category: .session)
                return true
            }
        } else {
            await MainActor.run { self.isStarting = false }
        }

        SecureLogger.warning("TorManager: ACTIVE resume failed; will restart", category: .session)
        return false
    }

    private func controlExchange(lines: [String], timeout: TimeInterval) async -> String? {
        guard let cookiePath = dataDirectoryURL()?.appendingPathComponent("control_auth_cookie"),
              let cookie = try? Data(contentsOf: cookiePath) else { return nil }
        let cookieHex = cookie.map { String(format: "%02X", $0) }.joined()

        let queue = DispatchQueue(label: "TorControl", qos: .userInitiated)
        let params = NWParameters.tcp
        guard let port = NWEndpoint.Port(rawValue: UInt16(controlPort)) else { return nil }
        let endpoint = NWEndpoint.hostPort(host: .ipv4(.loopback), port: port)
        let conn = NWConnection(to: endpoint, using: params)

        var resultText = ""
        var completed = false
        func send(_ text: String) {
            let data = (text + "\r\n").data(using: .utf8) ?? Data()
            conn.send(content: data, completion: .contentProcessed { _ in })
        }
        func receiveLoop(deadline: Date) async {
            while Date() < deadline {
                let ok: Bool = await withCheckedContinuation { cont in
                    conn.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, isComplete, error in
                        if let data = data, !data.isEmpty, let s = String(data: data, encoding: .utf8) {
                            resultText.append(s)
                        }
                        if isComplete || error != nil { completed = true }
                        cont.resume(returning: true)
                    }
                }
                if !ok || completed { break }
                // Small delay to avoid tight loop
                try? await Task.sleep(nanoseconds: 20_000_000)
            }
        }

        conn.start(queue: queue)
        // Send immediately; NWConnection will queue until ready
        send("AUTHENTICATE \(cookieHex)")
        // Send requested lines
        for line in lines { send(line) }
        // Ask tor to close
        send("QUIT")
        await receiveLoop(deadline: Date().addingTimeInterval(timeout))
        conn.cancel()
        return resultText
    }
}

// MARK: - Start policy configuration
extension TorManager {
    @MainActor
    public func setAutoStartAllowed(_ allow: Bool) {
        allowAutoStart = allow
    }

    @MainActor
    public func isAutoStartAllowed() -> Bool { allowAutoStart }
}
