//
//  SDLSuperClient.swift
//  Tun
//
//  Created by 安礼成 on 2026/2/13.
//

import Foundation
import Network

actor SDLSuperClient {
    enum State {
        case idle
        case running
        case stopped
    }
    
    private var state: State = .idle
    
    private let queue = DispatchQueue(label: "com.sdl.SuperClient.queue") // 专用队列保证线程安全
    
    // 数据流
    public let messageStream: AsyncThrowingStream<SDLQUICInboundMessage, Error>
    private let messageCont: AsyncThrowingStream<SDLQUICInboundMessage, Error>.Continuation
    private var isMessageContinuationFinished: Bool = false
    
    private var readTask: Task<Void, Never>?
    
    private let connection: NWConnection
    private let maxBufferSize: Int
    
    init(serverEndpoint: SDLConfiguration.ResolvedServerEndpoint, port: UInt16, maxBufferSize: Int = 2 * 1024 * 1024) {
        self.maxBufferSize = maxBufferSize
        
        let pairs = AsyncThrowingStream.makeStream(of: SDLQUICInboundMessage.self)
        self.messageStream = pairs.stream
        self.messageCont = pairs.continuation
        
        let options = NWProtocolTLS.Options()
        serverEndpoint.host.withCString {
            sec_protocol_options_set_tls_server_name(options.securityProtocolOptions, $0)
        }
        sec_protocol_options_add_tls_application_protocol(
            options.securityProtocolOptions,
            "punchnet/1.0"
        )
        
        // 这里设置证书的校验逻辑
        sec_protocol_options_set_verify_block(
            options.securityProtocolOptions,
            { _, trust, complete in
                // 执行公钥校验
                complete(SDLSuperTLSVerifier.verify(trust: trust, host: serverEndpoint.host))
            },
            queue
        )
        SDLLogger.log("[SDLSuperClient] start with tls protocol", for: .debug)
        
        let params = NWParameters(tls: options)
        // 关键：让 Network.framework 忽略系统代理
        params.preferNoProxies = true
        
        self.connection = NWConnection(host: Self.makeEndpointHost(address: serverEndpoint.ip), port: .init(rawValue: port)!, using: params)
    }
    
    func start() {
        self.connection.stateUpdateHandler = { [weak self] state in
            SDLLogger.log("[SDLSuperClient] new state: \(state)", for: .debug)
            Task {
                await self?.handleConnectionState(state: state)
            }
        }
        self.connection.start(queue: queue)
    }

    private static func makeEndpointHost(address ip: String) -> NWEndpoint.Host {
        if let ipv4Address = IPv4Address(ip) {
            return .ipv4(ipv4Address)
        }

        if let ipv6Address = IPv6Address(ip) {
            return .ipv6(ipv6Address)
        }

        preconditionFailure("invalid super server IP: \(ip)")
    }
    
    private func handleConnectionState(state: NWConnection.State) {
        switch state {
        case .ready:
            self.startReadTask()
            self.state = .running
        case .failed(let error):
            self.finishMessageContinuationIfNeed(throwing: .connectionFailed(error))
        case .cancelled:
            self.finishMessageContinuationIfNeed(throwing: .connectionCancelled)
        default:
            ()
        }
    }
    
    private func finishMessageContinuationIfNeed(throwing error: SDLSuperError?) {
        guard !self.isMessageContinuationFinished else {
            return
        }
        
        self.isMessageContinuationFinished = true
        if let error {
            self.messageCont.finish(throwing: error)
        } else {
            self.messageCont.finish()
        }
    }
    
    private func startReadTask() {
        self.readTask?.cancel()
        
        self.readTask = Task {
            let frameParser = SDLSuperFrameParser(maxBufferSize: self.maxBufferSize)
            do {
                while true {
                    try Task.checkCancellation()
                    let data = try await Self.readOnce(connection: self.connection)
                    let frames = try frameParser.parseFrames(data: data)
                    for frame in frames {
                        try Task.checkCancellation()
                        if let message = SDLSuperCodec.decode(frame: frame) {
                            self.messageCont.yield(message)
                        } else {
                            self.finishMessageContinuationIfNeed(throwing: .decodeError("invalid message"))
                        }
                    }
                }
            } catch let err {
                self.finishMessageContinuationIfNeed(throwing: .internalError(err))
            }
        }
    }
    
    func send(type: SDLPacketType, data: Data) {
        guard case .running = state, connection.state == .ready else {
            return
        }
        
        var len = UInt16(data.count + 1).bigEndian
        var packet = Data(Data(bytes: &len, count: 2))
        packet.append(type.rawValue)
        packet.append(data)
        
        connection.send(content: packet, completion: .contentProcessed { [weak self] error in
            if let error {
                Task {
                    SDLLogger.log("[SDLSuperClient] send data get error: \(error)", for: .debug)
                    await self?.finishMessageContinuationIfNeed(throwing: .writeFailed(error))
                }
            }
        })
    }
    
    private static func readOnce(connection: NWConnection) async throws -> Data {
        let readContinuation = OnceContinuation<Data, Error>()
        
        return try await withTaskCancellationHandler {
            try await withCheckedThrowingContinuation { cont in
                readContinuation.set(cont)
                connection.receive(minimumIncompleteLength: 1, maximumLength: 64 * 1024) { data, _, isComplete, error in
                    if let error {
                        readContinuation.resume(throwing: error)
                        return
                    }
                    
                    if isComplete {
                        readContinuation.resume(throwing: SDLSuperError.dataStreamClosed)
                    } else {
                        readContinuation.resume(returning: data ?? Data())
                    }
                }
            }
        } onCancel: {
            readContinuation.resume(throwing: CancellationError())
        }
    }
    
    func stop() {
        guard self.state != .stopped else {
            return
        }
        
        self.state = .stopped
        
        self.readTask?.cancel()
        self.readTask = nil

        let connection = self.connection
        connection.stateUpdateHandler = nil
        connection.cancel()

        self.finishMessageContinuationIfNeed(throwing: nil)
        
        SDLLogger.log("[SDLSuperClient] stopped")
    }
    
    deinit {
        SDLLogger.log("[SDLSuperClient] deinit")
    }
    
}
