import Foundation
import UIKit
import linphonesw
import AVFoundation

class LinphoneBridge {
    private var core: Core!
    private var isRegistered = false
    private var currentCall: Call?
    private let activity: UIViewController
    private let remoteViewAcquisitor: () -> UIView?
    private let localViewAcquisitor: () -> UIView?
    private let onRegistrationStateChanged: (Int) -> Void
    private let onCallStateChanged: (Int) -> Void
    
    private var registrationState: RegistrationState = .None {
        didSet {
            print("registrationState delegate: oldValue: \(oldValue), newValue: \(registrationState)")
            onRegistrationStateChanged(registrationState.rawValue)
        }
    }
    
    private var callState: Call.State = .Idle {
        didSet {
            print("callState delegate: oldValue: \(oldValue), newValue: \(callState)")
            onCallStateChanged(callState.rawValue)
        }
    }
    
    private static let TAG = "LinphoneBridge"
    
    init(
        activity: UIViewController,
        remoteViewAcquisitor: @escaping () -> UIView?,
        localViewAcquisitor: @escaping () -> UIView?,
        onRegistrationStateChanged: @escaping (Int) -> Void,
        onCallStateChanged: @escaping (Int) -> Void
    ) {
        self.activity = activity
        self.remoteViewAcquisitor = remoteViewAcquisitor
        self.localViewAcquisitor = localViewAcquisitor
        self.onRegistrationStateChanged = onRegistrationStateChanged
        self.onCallStateChanged = onCallStateChanged
    }
    
    func checkPermissions() -> Bool {
        let audioStatus = AVCaptureDevice.authorizationStatus(for: .audio)
        let videoStatus = AVCaptureDevice.authorizationStatus(for: .video)
        
        if audioStatus != .authorized || videoStatus != .authorized {
            // Request permissions
            AVCaptureDevice.requestAccess(for: .audio) { _ in }
            AVCaptureDevice.requestAccess(for: .video) { _ in }
            return false
        }
        
        return true
    }
    
    func initializeLinphone() {
        do {
            let factory = Factory.Instance
            core = try factory.createCore(configPath: nil, factoryConfigPath: nil, systemContext: nil)
            
            // Add core listener
            core.addDelegate(delegate: self)
            
            // Enable video
            core.videoCaptureEnabled = true
            core.videoDisplayEnabled = true
            core.videoActivationPolicy?.automaticallyInitiate = true
            core.videoActivationPolicy?.automaticallyAccept = true
            
            // DSCP settings
            core.sipDscp = 26  // AF31
            core.audioDscp = 46  // EF
            core.videoDscp = 36  // AF42
            
            core.videoAdaptiveJittcompEnabled = true
            
            // Bandwidth settings
            core.uploadBandwidth = 512
            core.downloadBandwidth = 1500
            
            // Configure audio codecs
            let preferredAudio = ["opus", "pcmu", "pcma"]
            for pt in core.audioPayloadTypes {
                let mime = pt.mimeType.lowercased()
                let enabled = preferredAudio.contains(mime)
                let ok = pt.enable(enabled: enabled) == 0
                print("\(LinphoneBridge.TAG) Change state of \(mime) to \(enabled) = \(ok)")
            }
            
            // Configure video codecs
            let preferredVideo = ["h264", "vp8"]
            for pt in core.videoPayloadTypes {
                let mime = pt.mimeType.lowercased()
                let enabled = preferredVideo.contains(mime)
                let ok = pt.enable(enabled: enabled) == 0
                print("\(LinphoneBridge.TAG) Change state of \(mime) to \(enabled) = \(ok)")
            }
            
            // Set bitrates
            core.getPayloadType(type: "opus", rate: -1, channels: 0)?.normalBitrate = 32
            core.getPayloadType(type: "h264", rate: -1, channels: 0)?.normalBitrate = 600
            core.getPayloadType(type: "vp8", rate: -1, channels: 0)?.normalBitrate = 600
            
            // Video settings
            let preferredVidDef = try factory.createVideoDefinition(width: 720, height: 1280)
            core.preferredVideoDefinition = preferredVidDef
            core.preferredFramerate = 30
            
            try core.start()
        } catch {
            print("Error initializing Linphone: \(error)")
        }
    }
    
    func register(username: String, password: String, serverIp: String, serverPort: Int) {
        do {
            let factory = Factory.Instance
            let authInfo = try factory.createAuthInfo(
                username: username,
                userid: nil,
                passwd: password,
                ha1: nil,
                realm: nil,
                domain: serverIp
            )
            core.addAuthInfo(info: authInfo)
            
            let identity = try factory.createAddress(addr: "sip:\(username)@\(serverIp)")
            let server = try factory.createAddress(addr: "sip:\(serverIp):\(serverPort)")
            
            let accountParams = try core.createAccountParams()
            try accountParams.setIdentityaddress(newValue: identity)
            try accountParams.setServeraddress(newValue: server)
            accountParams.registerEnabled = true
            
            let account = try core.createAccount(params: accountParams)
            try core.addAccount(account: account)
            core.defaultAccount = account
        } catch {
            print("Error registering: \(error)")
        }
    }
    
    func unregister() {
        core.clearAccounts()
        core.clearAllAuthInfo()
    }
    
    func makeCall(callTo: String, isVideoEnabled: Bool) throws {
        guard isRegistered else {
            throw NSError(domain: "LinphoneBridge", code: 1, userInfo: [NSLocalizedDescriptionKey: "Not registered"])
        }
        
        do {
            let factory = Factory.Instance
            guard let remoteAddress = try? factory.createAddress(addr: callTo) else {
                throw NSError(domain: "LinphoneBridge", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to create remote address"])
            }
            
            guard let callParams = try? core.createCallParams(call: nil) else {
                throw NSError(domain: "LinphoneBridge", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to create call params"])
            }
            
            callParams.videoEnabled = isVideoEnabled
            callParams.videoDirection = isVideoEnabled ? .SendRecv : .Inactive
            
            currentCall = try core.inviteAddressWithParams(addr: remoteAddress, params: callParams)
        } catch {
            throw error
        }
    }
    
    func answerCall() -> Bool {
        guard let call = currentCall else {
            return false
        }
        
        do {
            let callParams = try core.createCallParams(call: call)
            callParams.videoEnabled = true
            callParams.videoDirection = .SendRecv
            try call.acceptWithParams(params: callParams)
            return true
        } catch {
            print("Error answering call: \(error)")
            return false
        }
    }
    
    func hangupCall() -> Bool {
        do {
            try currentCall?.terminate()
            return true
        } catch {
            print("Error hanging up: \(error)")
            return false
        }
    }
    
    func toggleVideo() -> Bool {
        guard let call = currentCall else {
            return false
        }
        
        do {
            let params = try core.createCallParams(call: call)
            params.videoEnabled = !(call.currentParams?.videoEnabled ?? false)
            try call.update(params: params)
            return call.currentParams?.videoEnabled ?? false
        } catch {
            print("Error toggling video: \(error)")
            return false
        }
    }
    
    func toggleMicrophone() -> Bool {
        core.micEnabled = !core.micEnabled
        return core.micEnabled
    }
    
    func stop() {
        core.stop()
    }
    
    func inCall() -> Bool {
        return currentCall != nil
    }
    
    enum CallType {
        case audio
        case video
        case unknown
    }
    
    func callType() -> CallType {
        guard let params = currentCall?.currentParams else {
            return .unknown
        }
        
        if params.videoEnabled {
            return .video
        } else {
            return .audio
        }
    }
}

// MARK: - CoreDelegate
extension LinphoneBridge: CoreDelegate {
    func onAccountRegistrationStateChanged(core: Core, account: Account, state: RegistrationState, message: String) {
        registrationState = state
        
        switch state {
        case .Ok:
            isRegistered = true
        case .Failed, .None, .Progress:
            isRegistered = false
        default:
            break
        }
    }
    
    func onCallStateChanged(core: Core, call: Call, state: Call.State, message: String) {
        callState = state
        
        switch state {
        case .IncomingReceived:
            currentCall = call
            print("onCallStateChanged: INCOMING_RECEIVED \(call)")
            
        case .Connected:
            print("onCallStateChanged: CONNECTED \(call)")
            
            if let remoteView = remoteViewAcquisitor() {
                call.nativeVideoWindowId = UnsafeMutableRawPointer(Unmanaged.passUnretained(remoteView).toOpaque())
            } else {
                print("onCallStateChanged: CONNECTED remoteViewAcquisitor found nothing")
            }
            
            if let localView = localViewAcquisitor() {
                core.nativePreviewWindowId = UnsafeMutableRawPointer(Unmanaged.passUnretained(localView).toOpaque())
            } else {
                print("onCallStateChanged: CONNECTED localViewAcquisitor found nothing")
            }
            
        case .StreamsRunning:
            print("onCallStateChanged: STREAMS_RUNNING \(call)")
            
            if let remoteView = remoteViewAcquisitor() {
                call.nativeVideoWindowId = UnsafeMutableRawPointer(Unmanaged.passUnretained(remoteView).toOpaque())
            } else {
                print("onCallStateChanged: STREAMS_RUNNING remoteViewAcquisitor found nothing")
            }
            
            if let localView = localViewAcquisitor() {
                core.nativePreviewWindowId = UnsafeMutableRawPointer(Unmanaged.passUnretained(localView).toOpaque())
            } else {
                print("onCallStateChanged: STREAMS_RUNNING localViewAcquisitor found nothing")
            }
            
        case .End, .Error, .Released:
            currentCall = nil
            
        default:
            break
        }
    }
}
