import Foundation

protocol SignalingClientDelegate: AnyObject {
    func signalingDidConnect()
    func signalingDidDisconnect()
    func signaling(didFail error: String)
    func signaling(didReceive message: SignalMessage)
    /// 令牌失效（关闭码 4001）：需刷新令牌后重连。
    func signalingTokenExpired()
    /// 强制下线（关闭码 4003）：需停止重连并跳登录。
    func signalingForceLogout()
}

/// 信令 WebSocket 客户端（URLSessionWebSocketTask 实现）。
///
/// 鉴权方式：通过 URLRequest 在握手请求头携带 `Authorization: Bearer <accessToken>`。
/// 不再发送 REGISTER —— 连接由服务端根据令牌身份自动完成，并下发 REGISTER_SUCCESS。
final class SignalingClient: NSObject {

    weak var delegate: SignalingClientDelegate?

    private let serverUrl: String
    private let token: String?
    private var session: URLSession?
    private var task: URLSessionWebSocketTask?
    private var manuallyClosed = false
    /// WebSocket 握手是否已完成（didOpen 后置为 true）
    private var isOpened = false
    /// 是否已上报过断开/失败，避免重复回调
    private var didNotifyClosure = false
    /// 心跳定时器（每 25s 发送 PING）
    private var heartbeatTimer: Timer?

    init(serverUrl: String, token: String?) {
        self.serverUrl = serverUrl
        self.token = token
        super.init()
    }

    func connect() {
        guard let url = URL(string: serverUrl) else {
            notifyError("无效的服务器地址: \(serverUrl)")
            return
        }
        manuallyClosed = false
        isOpened = false
        didNotifyClosure = false

        // 握手携带 Bearer token（URLSessionWebSocketTask 不直接支持自定义头，需用 URLRequest）。
        var request = URLRequest(url: url)
        if let token, !token.isEmpty {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        let config = URLSessionConfiguration.default
        config.timeoutIntervalForRequest = 15
        session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
        task = session?.webSocketTask(with: request)
        // 接收循环在 didOpenWithProtocol（握手完成）后再启动，
        // 避免在 socket 未真正连接时调用 receive 触发 Code 57。
        task?.resume()
    }

    func disconnect() {
        manuallyClosed = true
        isOpened = false
        stopHeartbeat()
        task?.cancel(with: .normalClosure, reason: "Disconnecting".data(using: .utf8))
        task = nil
        session?.invalidateAndCancel()
        session = nil
    }

    func send(_ message: SignalMessage) {
        guard let task else { return }
        guard let data = try? JSONEncoder().encode(message),
              let json = String(data: data, encoding: .utf8) else { return }
        task.send(.string(json)) { error in
            if let error {
                NSLog("[Signaling] send error: \(error.localizedDescription)")
            }
        }
    }

    var isConnected: Bool { task != nil }

    // MARK: - Private

    private func startHeartbeat() {
        stopHeartbeat()
        heartbeatTimer = Timer.scheduledTimer(withTimeInterval: 25, repeats: true) { [weak self] _ in
            guard let self, let task = self.task else { return }
            task.send(.string("{\"type\":\"PING\"}")) { error in
                if let error { NSLog("[Signaling] ping error: \(error.localizedDescription)") }
            }
        }
    }

    private func stopHeartbeat() {
        heartbeatTimer?.invalidate()
        heartbeatTimer = nil
    }

    private func receiveLoop() {
        task?.receive { [weak self] result in
            guard let self else { return }
            switch result {
            case .success(let wsMessage):
                if case .string(let text) = wsMessage,
                   let data = text.data(using: .utf8),
                   let message = try? JSONDecoder().decode(SignalMessage.self, from: data) {
                    DispatchQueue.main.async {
                        self.delegate?.signaling(didReceive: message)
                    }
                }
                self.receiveLoop()
            case .failure(let error):
                self.handleReceiveFailure(error)
            }
        }
    }

    /// 处理接收失败：区分"正常断开"与"连接失败"。
    private func handleReceiveFailure(_ error: Error) {
        guard !manuallyClosed, !didNotifyClosure else { return }
        didNotifyClosure = true

        let nsError = error as NSError
        let isDisconnect = (nsError.domain == NSPOSIXErrorDomain && nsError.code == 57)
            || (nsError.domain == NSURLErrorDomain
                && (nsError.code == NSURLErrorNetworkConnectionLost
                || nsError.code == NSURLErrorCancelled))

        DispatchQueue.main.async {
            if self.isOpened || isDisconnect {
                self.delegate?.signalingDidDisconnect()
            } else {
                self.delegate?.signaling(didFail: error.localizedDescription)
            }
        }
    }

    private func notifyError(_ text: String) {
        DispatchQueue.main.async {
            self.delegate?.signaling(didFail: text)
        }
    }
}

// MARK: - URLSessionWebSocketDelegate

extension SignalingClient: URLSessionWebSocketDelegate {

    func urlSession(_ session: URLSession,
                    webSocketTask: URLSessionWebSocketTask,
                    didOpenWithProtocol protocol: String?) {
        isOpened = true
        // 握手完成后再启动接收循环，避免 socket 未连接时 receive 报错。
        // 不再发送 REGISTER，服务端根据 Bearer 令牌自动注册。
        receiveLoop()
        startHeartbeat()
        DispatchQueue.main.async {
            self.delegate?.signalingDidConnect()
        }
    }

    func urlSession(_ session: URLSession,
                    webSocketTask: URLSessionWebSocketTask,
                    didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
                    reason: Data?) {
        isOpened = false
        stopHeartbeat()
        guard !manuallyClosed, !didNotifyClosure else { return }
        didNotifyClosure = true

        let code = Int(closeCode.rawValue)
        if code == 4001 {
            DispatchQueue.main.async { self.delegate?.signalingTokenExpired() }
            return
        }
        if code == 4003 {
            DispatchQueue.main.async { self.delegate?.signalingForceLogout() }
            return
        }
        DispatchQueue.main.async {
            self.delegate?.signalingDidDisconnect()
        }
    }
}
