import Foundation
import Network

enum ConnectionType {
    case raw
    case secure
    case bridge
}

typealias ConnectionRecv = (Data) -> Void
typealias ConnectionReady = (Bool, ClientError?) -> Void

final class Connection {
    private let type: ConnectionType
    private let onRecv: ConnectionRecv
    private let onReady: ConnectionReady
    private var conn: NWConnection?

    // For raw connection
    init(type: ConnectionType, queue: DispatchQueue, onRecv: @escaping ConnectionRecv, onReady: @escaping ConnectionReady, host: String, port: Int) {
        self.type = type
        self.onRecv = onRecv
        self.onReady = onReady

        // params
        let tcpOptions = NWProtocolTCP.Options()
        tcpOptions.noDelay = true
        tcpOptions.connectionTimeout = 5
        tcpOptions.enableFastOpen = true
        tcpOptions.disableAckStretching = true
        let params = NWParameters(tls: nil, tcp: tcpOptions)
        params.serviceClass = .responsiveData

        // connection
        let conn = NWConnection(host: .name(host, nil), port: .init(integerLiteral: UInt16(port)), using: params)
        conn.stateUpdateHandler = { [weak self] state in
            switch state {
            case .ready:
                self?.onReady(true, nil)
                self?.read()

            case .waiting(let error), .failed(let error):
                print(error.localizedDescription)
                self?.onReady(false, .conInitError(error._code))

            default:
                break
            }
        }
        self.conn = conn
        self.conn?.start(queue: queue)
    }

    // For secured connection
    init(type: ConnectionType, queue: DispatchQueue, onRecv: @escaping ConnectionRecv, onReady: @escaping ConnectionReady, socketPath: URL, domain: String) {
        self.type = type
        self.onRecv = onRecv
        self.onReady = onReady

        // tcp options
        let tcpOptions = NWProtocolTCP.Options()
        tcpOptions.noDelay = true
        tcpOptions.enableFastOpen = true
        tcpOptions.disableAckStretching = true

        // tls options
        let tlsOptions = NWProtocolTLS.Options()
        sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
        sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv13)
        // sec_protocol_options_set_peer_authentication_required(tlsOptions.securityProtocolOptions, false)
        if let domain = domain.cString(using: .utf8) {
            sec_protocol_options_set_tls_server_name(tlsOptions.securityProtocolOptions, domain)
        }
        sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { _, sec_trust, sec_protocol_verify_complete in
            let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
            var error: CFError?
            if SecTrustEvaluateWithError(trust, &error) {
                sec_protocol_verify_complete(true)
            } else {
                sec_protocol_verify_complete(false)
            }
        }, queue)

        // secure connection
        let params = NWParameters(tls: tlsOptions, tcp: tcpOptions)
        params.serviceClass = .responsiveData
        let conn = NWConnection(to: .unix(path: socketPath.path), using: params)
        conn.stateUpdateHandler = { [weak self] state in
            switch state {
            case .ready:
                self?.onReady(true, nil)
                self?.read()

            case .waiting(let error), .failed(let error):
                print(error.localizedDescription)
                self?.onReady(false, .conTlsError(error._code))

            default:
                break
            }
        }
        self.conn = conn
        self.conn?.start(queue: queue)
    }

    init(type: ConnectionType, queue: DispatchQueue, onRecv: @escaping ConnectionRecv, onReady: @escaping ConnectionReady, nwConnection: NWConnection) {
        self.type = type
        self.onRecv = onRecv
        self.onReady = onReady
        conn = nwConnection
        conn?.stateUpdateHandler = { [weak self] state in
            switch state {
            case .ready:
                self?.onReady(true, nil)
                self?.read()

            case .waiting(let error), .failed(let error):
                print(error.localizedDescription)
                self?.onReady(false, .conInitError(error._code))

            default:
                break
            }
        }
        conn?.start(queue: queue)
    }

    deinit {
        conn?.cancel()
        conn = nil
    }

    func cancel() {
        conn?.cancel()
    }

    func write(data: Data) {
        log(data: data, read: false)
        conn?.send(content: data, completion: .contentProcessed { [weak self] error in
            if let err = error {
                self?.onReady(false, .conWriteError(err._code))
            }
        })
    }

    private func read() {
        conn?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, _, error in
            if let err = error {
                self?.onReady(false, .conReadError(err._code))
                return
            }
            guard let data else { return }
            self?.log(data: data, read: true)
            self?.onRecv(data)
            self?.read()
        })
    }

    private func log(data: Data, read: Bool) {
        let direction = read ? "r-fr-" : "w-to-"
        let src: String
        switch type {
        case .raw:
            src = "raw"

        case .bridge:
            src = "brg"

        case .secure:
            src = "sec"
        }

        #if DEBUG
            print("\(direction)\(src): \(String(bytes: data, encoding: .ascii) ?? "?")")
        #endif
    }
}
