import AVFAudio
import CryptoKit
import Foundation
import UIKit
import WebRTC

@MainActor
extension CallManager {

    func handleWebRtcPacket(_ packet: PacketWebRTC) async {
        guard uiState.phase == .webRtcExchange || uiState.phase == .active else { return }
        guard let peerConnection = self.peerConnection else { return }

        switch packet.signalType {
        case .answer:
            guard let answer = parseSessionDescription(from: packet.sdpOrCandidate),
                  answer.type == .answer else {
                return
            }
            do {
                try await setRemoteDescription(answer, on: peerConnection)
                remoteDescriptionSet = true
                await flushBufferedRemoteCandidates()
            } catch {
                finishCall(reason: "Failed to apply answer", notifyPeer: false)
            }
        case .offer:
            guard let offer = parseSessionDescription(from: packet.sdpOrCandidate),
                  offer.type == .offer else {
                return
            }
            do {
                try await setRemoteDescription(offer, on: peerConnection)
                remoteDescriptionSet = true
                await flushBufferedRemoteCandidates()

                let answer = try await createAnswer(on: peerConnection)
                try await setLocalDescription(answer, on: peerConnection)
                ProtocolManager.shared.sendWebRtcSignal(
                    signalType: .answer,
                    sdpOrCandidate: serializeSessionDescription(answer)
                )
            } catch {
                finishCall(reason: "Failed to handle offer", notifyPeer: false)
            }
        case .iceCandidate:
            guard let candidate = parseIceCandidate(from: packet.sdpOrCandidate) else { return }
            if !remoteDescriptionSet {
                bufferedRemoteCandidates.append(candidate)
                return
            }
            try? await peerConnection.add(candidate)
        }
    }

    func ensurePeerConnectionAndOffer() async {
        do {
            try configureAudioSession()
            let peerConnection = try ensurePeerConnection()
            applySenderCryptorIfPossible()

            let offer = try await createOffer(on: peerConnection)
            try await setLocalDescription(offer, on: peerConnection)
            ProtocolManager.shared.sendWebRtcSignal(
                signalType: .offer,
                sdpOrCandidate: serializeSessionDescription(offer)
            )
            offerSent = true
        } catch {
            finishCall(reason: "Failed to establish call", notifyPeer: false)
        }
    }

    func beginCallSession(peerPublicKey: String, title: String, username: String) {
        finishCall(reason: nil, notifyPeer: false)
        uiState = CallUiState(
            phase: .idle,
            peerPublicKey: peerPublicKey,
            peerTitle: title,
            peerUsername: username
        )
    }

    func finishCall(reason: String?, notifyPeer: Bool) {
        cancelRingTimeout()
        endLiveActivity()
        let wasActive = uiState.phase == .active
        if wasActive {
            CallSoundManager.shared.playEndCall()
        } else {
            CallSoundManager.shared.stopAll()
        }

        let snapshot = uiState
        if notifyPeer,
           ownPublicKey.isEmpty == false,
           snapshot.peerPublicKey.isEmpty == false,
           snapshot.phase != .idle {
            ProtocolManager.shared.sendCallSignal(
                signalType: .endCall,
                src: ownPublicKey,
                dst: snapshot.peerPublicKey
            )
        }

        if role == .caller,
           snapshot.peerPublicKey.isEmpty == false {
            let duration = max(snapshot.durationSec, 0)
            let peerKey = snapshot.peerPublicKey
            Task { @MainActor in
                try? await SessionManager.shared.sendCallAttachment(
                    toPublicKey: peerKey,
                    durationSec: duration,
                    opponentTitle: snapshot.peerTitle,
                    opponentUsername: snapshot.peerUsername
                )
                // Force immediate cache refresh so call attachment appears in chat
                MessageRepository.shared.refreshDialogCache(for: peerKey)
            }
        }

        durationTask?.cancel()
        durationTask = nil

        if let localAudioSender {
            WebRTCFrameCryptorBridge.detach(localAudioSender)
        }
        if let currentPeerConnection = self.peerConnection {
            for receiver in currentPeerConnection.receivers {
                WebRTCFrameCryptorBridge.detach(receiver)
            }
            currentPeerConnection.close()
        }

        localAudioTrack = nil
        localAudioSource = nil
        localAudioSender = nil
        self.peerConnection = nil
        bufferedRemoteCandidates.removeAll()
        attachedReceiverIds.removeAll()

        role = nil
        roomId = ""
        localPrivateKey = nil
        localPublicKeyHex = ""
        sharedKey = nil
        offerSent = false
        remoteDescriptionSet = false
        lastPeerSharedPublicHex = ""

        uiState = CallUiState()
        if let reason, !reason.isEmpty {
            uiState.statusText = reason
        }

        deactivateAudioSession()
    }

    func ensureLocalSessionKeys() {
        guard localPrivateKey == nil else { return }
        let key = Curve25519.KeyAgreement.PrivateKey()
        localPrivateKey = key
        localPublicKeyHex = key.publicKey.rawRepresentation.hexString
    }

    func hydratePeerIdentity(for publicKey: String) {
        if let dialog = DialogRepository.shared.dialogs[publicKey] {
            if uiState.peerTitle.isEmpty {
                uiState.peerTitle = dialog.opponentTitle
            }
            if uiState.peerUsername.isEmpty {
                uiState.peerUsername = dialog.opponentUsername
            }
        }
    }

    func applySenderCryptorIfPossible() {
        guard let sharedKey, sharedKey.count >= CallMediaCrypto.keyLength else { return }
        guard let localAudioSender else { return }
        _ = WebRTCFrameCryptorBridge.attach(localAudioSender, sharedKey: sharedKey)
    }

    func startDurationTimerIfNeeded() {
        durationTask?.cancel()
        durationTask = Task { [weak self] in
            while !Task.isCancelled {
                try? await Task.sleep(for: .seconds(1))
                guard !Task.isCancelled else { return }
                await MainActor.run {
                    guard let self else { return }
                    guard self.uiState.phase == .active else { return }
                    self.uiState.durationSec += 1
                    if self.uiState.durationSec % 2 == 0 {
                        self.updateLiveActivity()
                    }
                }
            }
        }
    }

    // MARK: - WebRTC setup

    func ensurePeerConnection() throws -> RTCPeerConnection {
        if let currentPeerConnection = self.peerConnection { return currentPeerConnection }

        if peerConnectionFactory == nil {
            RTCPeerConnectionFactory.initialize()
            peerConnectionFactory = RTCPeerConnectionFactory()
        }
        let factory = peerConnectionFactory ?? RTCPeerConnectionFactory()

        let configuration = RTCConfiguration()
        if iceServers.isEmpty {
            configuration.iceServers = [RTCIceServer(urlStrings: ["stun:stun.l.google.com:19302"])]
        } else {
            configuration.iceServers = iceServers
        }
        configuration.sdpSemantics = .unifiedPlan

        let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
        guard let connection = factory.peerConnection(
            with: configuration,
            constraints: constraints,
            delegate: self
        ) else {
            throw NSError(domain: "CallManager", code: -3)
        }

        let audioSource = factory.audioSource(with: constraints)
        let audioTrack = factory.audioTrack(with: audioSource, trackId: "rosetta_audio_track")
        audioTrack.isEnabled = !uiState.isMuted
        localAudioSender = connection.add(audioTrack, streamIds: ["rosetta_audio_stream"])

        self.localAudioSource = audioSource
        self.localAudioTrack = audioTrack
        self.peerConnection = connection
        return connection
    }

    func configureAudioSession() throws {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(
            .playAndRecord,
            mode: .voiceChat,
            options: [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker]
        )
        try session.setActive(true)
        applyAudioOutputRouting()
        UIDevice.current.isProximityMonitoringEnabled = true
        print("[Call] AudioSession configured: category=\(session.category.rawValue) mode=\(session.mode.rawValue) sampleRate=\(session.sampleRate) outputs=\(session.currentRoute.outputs.map { "\($0.portName)(\($0.portType.rawValue))" }) inputs=\(session.currentRoute.inputs.map { "\($0.portName)(\($0.portType.rawValue))" })")
    }

    func deactivateAudioSession() {
        UIDevice.current.isProximityMonitoringEnabled = false
        try? AVAudioSession.sharedInstance().setActive(false, options: [.notifyOthersOnDeactivation])
    }

    func applyAudioOutputRouting() {
        let session = AVAudioSession.sharedInstance()
        if uiState.isSpeakerOn {
            try? session.overrideOutputAudioPort(.speaker)
        } else {
            try? session.overrideOutputAudioPort(.none)
        }
    }

    // MARK: - SDP / ICE

    private func createOffer(on peerConnection: RTCPeerConnection) async throws -> RTCSessionDescription {
        try await withCheckedThrowingContinuation { continuation in
            let constraints = RTCMediaConstraints(
                mandatoryConstraints: ["OfferToReceiveAudio": "true"],
                optionalConstraints: nil
            )
            peerConnection.offer(for: constraints) { sdp, error in
                if let error {
                    continuation.resume(throwing: error)
                    return
                }
                guard let sdp else {
                    continuation.resume(throwing: NSError(domain: "CallManager", code: -1))
                    return
                }
                continuation.resume(returning: sdp)
            }
        }
    }

    private func createAnswer(on peerConnection: RTCPeerConnection) async throws -> RTCSessionDescription {
        try await withCheckedThrowingContinuation { continuation in
            let constraints = RTCMediaConstraints(
                mandatoryConstraints: ["OfferToReceiveAudio": "true"],
                optionalConstraints: nil
            )
            peerConnection.answer(for: constraints) { sdp, error in
                if let error {
                    continuation.resume(throwing: error)
                    return
                }
                guard let sdp else {
                    continuation.resume(throwing: NSError(domain: "CallManager", code: -2))
                    return
                }
                continuation.resume(returning: sdp)
            }
        }
    }

    private func setLocalDescription(_ sdp: RTCSessionDescription, on peerConnection: RTCPeerConnection) async throws {
        try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
            peerConnection.setLocalDescription(sdp) { error in
                if let error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: ())
                }
            }
        }
    }

    private func setRemoteDescription(_ sdp: RTCSessionDescription, on peerConnection: RTCPeerConnection) async throws {
        try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
            peerConnection.setRemoteDescription(sdp) { error in
                if let error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: ())
                }
            }
        }
    }

    private func flushBufferedRemoteCandidates() async {
        guard let currentPeerConnection = self.peerConnection else { return }
        guard !bufferedRemoteCandidates.isEmpty else { return }
        for candidate in bufferedRemoteCandidates {
            try? await currentPeerConnection.add(candidate)
        }
        bufferedRemoteCandidates.removeAll()
    }

    private func serializeSessionDescription(_ sdp: RTCSessionDescription) -> String {
        let payload: [String: Any] = [
            "type": serializeSdpType(sdp.type),
            "sdp": sdp.sdp,
        ]
        guard let data = try? JSONSerialization.data(withJSONObject: payload),
              let raw = String(data: data, encoding: .utf8) else {
            return ""
        }
        return raw
    }

    private func parseSessionDescription(from raw: String) -> RTCSessionDescription? {
        guard let data = raw.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let typeRaw = json["type"] as? String,
              let sdp = json["sdp"] as? String else {
            return nil
        }

        let type: RTCSdpType
        switch typeRaw.lowercased() {
        case "offer": type = .offer
        case "answer": type = .answer
        case "pranswer": type = .prAnswer
        case "rollback": type = .rollback
        default: return nil
        }

        return RTCSessionDescription(type: type, sdp: sdp)
    }

    private func serializeSdpType(_ type: RTCSdpType) -> String {
        switch type {
        case .offer:
            return "offer"
        case .answer:
            return "answer"
        case .prAnswer:
            return "pranswer"
        case .rollback:
            return "rollback"
        @unknown default:
            return "offer"
        }
    }

    private func parseIceCandidate(from raw: String) -> RTCIceCandidate? {
        guard let data = raw.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let candidate = json["candidate"] as? String else {
            return nil
        }
        let sdpMid = json["sdpMid"] as? String
        let lineIndex = (json["sdpMLineIndex"] as? NSNumber)?.int32Value ?? 0
        return RTCIceCandidate(sdp: candidate, sdpMLineIndex: lineIndex, sdpMid: sdpMid)
    }
}

// MARK: - RTCPeerConnectionDelegate

extension CallManager: RTCPeerConnectionDelegate {
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {}
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
        Task { @MainActor in
            print("[Call] Remote stream added: \(stream.streamId), audioTracks=\(stream.audioTracks.count), videoTracks=\(stream.videoTracks.count)")
            for audioTrack in stream.audioTracks {
                audioTrack.isEnabled = true
                print("[Call] Remote audio track: \(audioTrack.trackId), enabled=\(audioTrack.isEnabled), state=\(audioTrack.readyState.rawValue)")
            }
        }
    }
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {}
    nonisolated func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {}
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
        Task { @MainActor in
            self.handleIceConnectionStateChanged(newState)
        }
    }
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {}
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
        Task { @MainActor in
            self.handleGeneratedCandidate(candidate)
        }
    }
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {}
    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {}

    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCPeerConnectionState) {
        print("[Call] PeerConnection state: \(newState.rawValue)")
        if newState == .connected {
            Task { @MainActor in
                self.setCallActiveIfNeeded()
                // Log audio state
                let localEnabled = self.localAudioTrack?.isEnabled ?? false
                let senderCount = peerConnection.senders.count
                let receiverCount = peerConnection.receivers.count
                let transceiverCount = peerConnection.transceivers.count
                print("[Call] CONNECTED — localAudio=\(localEnabled), senders=\(senderCount), receivers=\(receiverCount), transceivers=\(transceiverCount)")
                for (i, t) in peerConnection.transceivers.enumerated() {
                    let dir = t.direction.rawValue
                    let hasSender = t.sender.track != nil
                    let hasReceiver = t.receiver.track != nil
                    let recvEnabled = t.receiver.track?.isEnabled ?? false
                    print("[Call] Transceiver[\(i)]: dir=\(dir) hasSender=\(hasSender) hasReceiver=\(hasReceiver) recvEnabled=\(recvEnabled)")
                }
                // Log audio session
                let session = AVAudioSession.sharedInstance()
                print("[Call] AudioSession: category=\(session.category.rawValue) mode=\(session.mode.rawValue) route.outputs=\(session.currentRoute.outputs.map { $0.portName })")
            }
            return
        }
        if newState == .failed || newState == .closed || newState == .disconnected {
            Task { @MainActor in self.finishCall(reason: "Connection lost", notifyPeer: false) }
        }
    }

    nonisolated func peerConnection(_ peerConnection: RTCPeerConnection, didStartReceivingOn transceiver: RTCRtpTransceiver) {
        guard let receiver = transceiver.receiver as RTCRtpReceiver? else { return }
        Task { @MainActor in
            receiver.track?.isEnabled = true
            let trackKind = transceiver.mediaType == .audio ? "audio" : "video"
            print("[Call] didStartReceivingOn: \(trackKind), receiverId=\(receiver.receiverId), trackEnabled=\(receiver.track?.isEnabled ?? false)")
            self.attachReceiverCryptor(receiver)
        }
    }
}
