import Foundation
import NucleicProtocol

#if canImport(Network)
import Network

/// The LAN transport (SYNC_PROTOCOL §3.1): advertises `_nucleic._tcp` via Bonjour and
/// accepts TCP connections, wrapping each in a `LANChannel`. TLS is *not* relied on for
/// secrecy — Noise provides E2EE + mutual auth above this; the TCP layer is just bytes.
public final class LANListener: SyncListener, @unchecked Sendable {
    public static let serviceType = "_nucleic._tcp"

    private let port: NWEndpoint.Port
    private let fingerprint: String
    private let queue = DispatchQueue(label: "nucleic.sync.listener")
    private var listener: NWListener?
    private var continuation: AsyncStream<any FrameChannel>.Continuation?

    /// - Parameters:
    ///   - port: `.any` lets the OS pick; pass a fixed port to advertise it in the QR.
    ///   - identityFingerprint: the host static-key fingerprint, advertised in the TXT record
    ///     so a browsing client can sanity-check before connecting (structural metadata only).
    public init(port: NWEndpoint.Port = .any, identityFingerprint: String) {
        self.port = port
        self.fingerprint = identityFingerprint
    }

    public func start() throws -> AsyncStream<any FrameChannel> {
        let params = NWParameters.tcp
        params.includePeerToPeer = true
        let listener: NWListener
        do {
            listener = try NWListener(using: params, on: port)
        } catch {
            throw SyncTransportError.listenerFailed("\(error)")
        }
        self.listener = listener

        let txt = NWTXTRecord(["fp": fingerprint, "v": String(SyncProtocol.version)])
        listener.service = NWListener.Service(type: Self.serviceType, txtRecord: txt)

        let stream = AsyncStream<any FrameChannel> { continuation in
            self.continuation = continuation
        }
        listener.newConnectionHandler = { [weak self] connection in
            guard let self else { return }
            let channel = LANChannel(connection: connection, queue: self.queue)
            self.continuation?.yield(channel)
        }
        listener.stateUpdateHandler = { [weak self] state in
            if case .failed = state { self?.continuation?.finish() }
        }
        listener.start(queue: queue)
        return stream
    }

    public func stop() {
        listener?.cancel()
        listener = nil
        continuation?.finish()
    }

    /// The OS-assigned port once the listener is ready (for the pairing QR's LAN hint).
    public var assignedPort: UInt16? { listener?.port?.rawValue }
}

/// Wraps one `NWConnection` as a `FrameChannel`: de-frames inbound bytes with a
/// `FrameAccumulator`, length-prefixes outbound frames. Starts receiving immediately so no
/// inbound bytes are missed before `frames()` is consumed.
public final class LANChannel: FrameChannel, @unchecked Sendable {
    public let peerDescription: String
    private let connection: NWConnection
    private let queue: DispatchQueue
    private let accumulator = FrameAccumulator()
    private let stream: AsyncStream<Data>
    private let continuation: AsyncStream<Data>.Continuation

    init(connection: NWConnection, queue: DispatchQueue) {
        self.connection = connection
        self.queue = queue
        switch connection.endpoint {
        case .hostPort(let host, let port): peerDescription = "\(host):\(port)"
        default: peerDescription = "\(connection.endpoint)"
        }
        var cont: AsyncStream<Data>.Continuation!
        stream = AsyncStream(bufferingPolicy: .unbounded) { cont = $0 }
        continuation = cont

        connection.stateUpdateHandler = { [weak self] state in
            switch state {
            case .failed, .cancelled: self?.continuation.finish()
            default: break
            }
        }
        connection.start(queue: queue)
        receiveLoop()
    }

    public func frames() -> AsyncStream<Data> { stream }

    public func send(_ frame: Data) {
        connection.send(content: WireFraming.frame(frame), completion: .idempotent)
    }

    public func close() {
        connection.cancel()
        continuation.finish()
    }

    private func receiveLoop() {
        connection.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { [weak self] data, _, isComplete, error in
            guard let self else { return }
            if let data, !data.isEmpty {
                if let frames = try? self.accumulator.push(data) {
                    for frame in frames { self.continuation.yield(frame) }
                } else {
                    // Oversize/garbage frame → drop the connection.
                    self.close(); return
                }
            }
            if isComplete || error != nil {
                self.continuation.finish()
                return
            }
            self.receiveLoop()
        }
    }
}
#endif
