import Flutter
import UIKit

public class LiblinphoneFlutterPlugin: NSObject, FlutterPlugin {
    private var channel: FlutterMethodChannel!
    private var registrationEventsChannel: FlutterEventSink?
    private var callEventsChannel: FlutterEventSink?
    
    private var remoteViewCache: [Int: UIView] = [:]
    private var localViewCache: [Int: UIView] = [:]
    
    private var linphoneBridge: LinphoneBridge!
    
    private static let TAG = "LiblinphoneFlutterPlugin"
    
    public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(
            name: "liblinphone_flutter",
            binaryMessenger: registrar.messenger()
        )
        let instance = LiblinphoneFlutterPlugin()
        instance.channel = channel
        registrar.addMethodCallDelegate(instance, channel: channel)
        
        // Register platform views
        let remoteViewFactory = RemoteViewFactory(
            messenger: registrar.messenger(),
            cacher: { view in
                print("[\(TAG)] Caching RemoteView")
                instance.remoteViewCache[0] = view
            }
        )
        registrar.register(
            remoteViewFactory,
            withId: "liblinphone_flutter.nuark.xyz/remote_view"
        )
        
        let localViewFactory = LocalViewFactory(
            messenger: registrar.messenger(),
            cacher: { view in
                print("[\(TAG)] Caching LocalView")
                instance.localViewCache[0] = view
            }
        )
        registrar.register(
            localViewFactory,
            withId: "liblinphone_flutter.nuark.xyz/local_view"
        )
        
        // Setup event channels
        let registrationEventChannel = FlutterEventChannel(
            name: "liblinphone_flutter.nuark.xyz/registration_events",
            binaryMessenger: registrar.messenger()
        )
        let registrationStreamHandler = EventStreamHandler(
            onListen: { sink in
                instance.registrationEventsChannel = sink
            },
            onCancel: {
                instance.registrationEventsChannel = nil
            }
        )
        registrationEventChannel.setStreamHandler(registrationStreamHandler)
        
        let callEventChannel = FlutterEventChannel(
            name: "liblinphone_flutter.nuark.xyz/call_events",
            binaryMessenger: registrar.messenger()
        )
        let callStreamHandler = EventStreamHandler(
            onListen: { sink in
                instance.callEventsChannel = sink
            },
            onCancel: {
                instance.callEventsChannel = nil
            }
        )
        callEventChannel.setStreamHandler(callStreamHandler)
    }
    
    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "checkPermissions":
            let hasPermissions = linphoneBridge.checkPermissions()
            result(hasPermissions)
            
        case "initialize":
            do {                
                linphoneBridge = LinphoneBridge(
                    remoteViewAcquisitor: { [weak self] in
                        return self?.acquireRemoteView()
                    },
                    localViewAcquisitor: { [weak self] in
                        return self?.acquireLocalView()
                    },
                    onRegistrationStateChanged: { [weak self] state in
                        self?.registrationEventsChannel?(state)
                    },
                    onCallStateChanged: { [weak self] state in
                        self?.callEventsChannel?(state)
                    }
                )
                linphoneBridge.initializeLinphone()
                result(true)
            } catch {
                print("[\(LiblinphoneFlutterPlugin.TAG)] initialize error: \(error.localizedDescription)")
                result(FlutterError(
                    code: "ERROR",
                    message: error.localizedDescription,
                    details: nil
                ))
            }
            
        case "register":
            guard let args = call.arguments as? [String: Any],
                  let username = args["username"] as? String,
                  let password = args["password"] as? String,
                  let serverIp = args["serverIp"] as? String,
                  let serverPort = args["serverPort"] as? Int else {
                result(FlutterError(
                    code: "INVALID_ARGUMENTS",
                    message: "Missing required arguments",
                    details: nil
                ))
                return
            }
            
            do {
                linphoneBridge.register(
                    username: username,
                    password: password,
                    serverIp: serverIp,
                    serverPort: serverPort
                )
                result(true)
            } catch {
                print("[\(LiblinphoneFlutterPlugin.TAG)] register error: \(error.localizedDescription)")
                result(FlutterError(
                    code: "ERROR",
                    message: error.localizedDescription,
                    details: nil
                ))
            }
            
        case "unregister":
            linphoneBridge.unregister()
            result(true)
            
        case "makeCall":
            guard let args = call.arguments as? [String: Any],
                  let callTo = args["callTo"] as? String,
                  let isVideoEnabled = args["isVideoEnabled"] as? Bool else {
                result(FlutterError(
                    code: "INVALID_ARGUMENTS",
                    message: "Missing required arguments",
                    details: nil
                ))
                return
            }
            
            do {
                try linphoneBridge.makeCall(callTo: callTo, isVideoEnabled: isVideoEnabled)
                result(true)
            } catch {
                print("[\(LiblinphoneFlutterPlugin.TAG)] makeCall error: \(error.localizedDescription)")
                result(FlutterError(
                    code: "ERROR",
                    message: error.localizedDescription,
                    details: nil
                ))
            }
            
        case "answerCall":
            let success = linphoneBridge.answerCall()
            result(success)
            
        case "hangupCall":
            let success = linphoneBridge.hangupCall()
            result(success)
            
        case "inCall":
            let inCall = linphoneBridge.inCall()
            result(inCall)
            
        case "callType":
            let callType = linphoneBridge.callType()
            let ordinal: Int
            switch callType {
            case .audio:
                ordinal = 0
            case .video:
                ordinal = 1
            case .unknown:
                ordinal = 2
            }
            result(ordinal)
            
        case "toggleVideo":
            let enabled = linphoneBridge.toggleVideo()
            result(enabled)
            
        case "toggleMicrophone":
            let enabled = linphoneBridge.toggleMicrophone()
            result(enabled)
            
        case "stop":
            linphoneBridge.stop()
            result(true)
            
        default:
            result(FlutterMethodNotImplemented)
        }
    }
    
    internal func acquireLocalView() -> UIView? {
        print("[\(LiblinphoneFlutterPlugin.TAG)] acquireLocalView: \(localViewCache.count)")
        return localViewCache[0]
    }
    
    internal func acquireRemoteView() -> UIView? {
        print("[\(LiblinphoneFlutterPlugin.TAG)] acquireRemoteView: \(remoteViewCache.count)")
        return remoteViewCache[0]
    }
}

// MARK: - Event Stream Handler
class EventStreamHandler: NSObject, FlutterStreamHandler {
    private let onListenCallback: (@escaping FlutterEventSink) -> Void
    private let onCancelCallback: () -> Void
    
    init(onListen: @escaping (@escaping FlutterEventSink) -> Void, onCancel: @escaping () -> Void) {
        self.onListenCallback = onListen
        self.onCancelCallback = onCancel
    }
    
    func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        onListenCallback(events)
        return nil
    }
    
    func onCancel(withArguments arguments: Any?) -> FlutterError? {
        onCancelCallback()
        return nil
    }
}
