import Foundation
import Capacitor
import LunaCore
import LunaCamera
import LunaWeb

/**
 * Please read the Capacitor iOS Plugin Development Guide
 * here: https://capacitorjs.com/docs/plugins/ios
 */
@objc(LunaSDKPlugin)
public class LunaSDKPlugin: CAPPlugin, LMCameraDelegate, LMCameraCaptureManagerDelegate {
    private lazy var call: CAPPluginCall = CAPPluginCall();
    private var configuration: LCLunaConfiguration!
    private var livenessAPI: LCOnlineLivenessProtocol!
    private var viewController: LMCameraViewControllerProtocol!
    private var initialized: Bool = false
    private var initializing: Bool = false
    private var recordVideo: Bool!
    private lazy var licenseConfig: LCLicenseConfig = LCLicenseConfig.userDefaults()


    override public func load() {
        checkAndResetLicenseCache()

        let configuration = LCLunaConfiguration.userDefaults()
        configuration.multipartBestShotsEnabled = false
        configuration.occludeCheck = false
        configuration.glassesCheckEnabled = false
        configuration.interactionEnabled = false
        configuration.trackFaceIdentity = false
        configuration.faceOcclusionEstimatorEnabled = false
        configuration.aggregationEnabled = false
        configuration.eyeInjury = false
        configuration.faceTime = 2
        configuration.interactionsConfig.interactionTimeout = 15
        configuration.interactionsConfig.timeoutBetweenInteractions = 1
        configuration.interactionsConfig.stepsNumber = 2
        configuration.compressionQuality = 1

        // BestShot configuration
        configuration.bestShotConfiguration.livenessType = .none
        configuration.bestShotConfiguration.eyesCheck = true
        configuration.bestShotConfiguration.ags = 0.5
        configuration.bestShotConfiguration.headYaw = 20
        configuration.bestShotConfiguration.headRoll = 20
        configuration.bestShotConfiguration.headPitch = 20
        configuration.bestShotConfiguration.numberOfBestShots = 1
        configuration.bestShotConfiguration.livenessQuality = 0.9

        if let licensePath = Bundle.main.path(forResource: "vllicense", ofType: "plist") {
            print("License file \(licensePath)")
            licenseConfig = LCLicenseConfig.init(plistFilePath: licensePath)
        } else {
            print("License file not found")
        }

        self.configuration = configuration
    }

    func saveCall(_ call: CAPPluginCall){
        call.keepAlive = true
        bridge?.saveCall(call)
        self.call = call
    }

    func releaseCall(){
        call.keepAlive = false
        bridge?.releaseCall(call)
    }

    @objc func initialize(_ call: CAPPluginCall) {
        guard !initializing else {
            call.resolve(["initializing": true])
            return
        }
        guard !initialized else {
            call.resolve(["initialized": true])
            return
        }
        guard let lunaServerURL = Bundle.main.object(forInfoDictionaryKey: "LunaServerURL") as? String else {
            let message = "Error: Unable to retrieve LunaServerURL from Info.plist"
            call.reject(message)
            return
        }
        guard let lunaAccountID = Bundle.main.object(forInfoDictionaryKey: "LunaAccountID") as? String else {
            let message = "Error: Unable to retrieve LunaAccountID from Info.plist"
            call.reject(message)
            return
        }

        // TODO: Add lunaConfig support
        let headers = call.getObject("headers") as? [String: String] ?? [:]
        print("headers: \(headers)")



        let additionalHeaders: HeadersHandler = { _ in
                return headers
        }

        //        configuration.occludeCheck = call.getBool("occludeCheck", configuration.occludeCheck)
        //        configuration.trackFaceIdentity = call.getBool("trackFaceIdentity", configuration.trackFaceIdentity)
        //        configuration.bestShotConfiguration.eyesCheck = call.getBool("eyesCheck", configuration.bestShotConfiguration.eyesCheck)
        //        configuration.glassesCheckEnabled = call.getBool("glassesCheckEnabled", configuration.glassesCheckEnabled)
        //        configuration.uploadImagesForLiveness = call.getBool("livenessEnabled", configuration.uploadImagesForLiveness)
        configuration.interactionEnabled = call.getBool("interactionEnabled", configuration.interactionEnabled)
        configuration.plistLicenseFileName = call.getString("plistLicenseFileName", configuration.plistLicenseFileName)

        if (configuration.interactionEnabled){
            var timeout = call.getInt("interactionTimeout")
            timeout = timeout != nil ? timeout! / 1000 : Int(configuration.interactionsConfig.interactionTimeout)
            print("interactionTimeout:  \(timeout!)")
            configuration.interactionsConfig.interactionTimeout = TimeInterval(timeout!)
        }

        print("lunaSDKVersion: \(configuration.lunaSDKVersion())")
        print("lunaIDSDKVersion: \(configuration.lunaIDSDKVersion())")
        print("interactionEnabled: \(configuration.interactionEnabled)")
        print("plistLicenseFileName: \(configuration.plistLicenseFileName)")
        print("License server: \(licenseConfig.server)")
        print("License eID: \(licenseConfig.eid)")
        print("License productID: \(licenseConfig.productID)")
        print("License filename: \(licenseConfig.filename)")
        print("License connectionTimeout: \(licenseConfig.connectionTimeout)")
        print("License containerMode: \(licenseConfig.containerMode)")
        print("License useZeus: \(licenseConfig.useZeus)")

        initializing = true

        let lunaIDServiceResult = LCLunaIDServiceBuilder.buildLunaIDService(withConfig: configuration)
        if let error = lunaIDServiceResult.error {
            call.reject("Error: Unable to create LunaIDService", "\(error.localizedDescription)")
            initializing = false
            return
        }
        guard lunaIDServiceResult.value != nil else {
            call.reject("Error: Unable to create LunaIDService")
            initializing = false
            return
        }

        let lwConfig = LWConfig.init()
        lwConfig.lunaAccountID = lunaAccountID
        lwConfig.platformURL = URL(string: lunaServerURL)

        livenessAPI = LivenessAPIv6(
            config: lwConfig,
            compressionQuality: 1.0,
            livenessQuality: 0.9,
            additionalHeaders: additionalHeaders,
        )


        print("Calling LCLunaIDServiceBuilder.activateLicense(withConfig:license:)...")
        if let error = LCLunaIDServiceBuilder.activateLicense(withConfig: configuration, license: licenseConfig) {
            debugPrint(
                "Error while checking license: \((error as NSError).code) - \((error as NSError).localizedDescription)"
            )
            initialized = false
            initializing = false
            call.reject("License activation error", "ERROR")
        } else {
            print("SUCCESS on LCLunaIDServiceBuilder.activateLicense(withConfig:license:)")
            initialized = true
            initializing = false
            call.resolve(["initialized": true])
        }
    }

    @objc func start(_ call: CAPPluginCall) {
        guard initialized else {
            call.reject("Configurations not initialized. call initialize() first", "NOT_INITIALIZED")
            return
        }
        guard bridge != nil else { return }
        guard let livenessAPI = livenessAPI else { return }
        saveCall(call)

        recordVideo = call.getBool("recordVideo", false) || false
        if recordVideo {
            configuration.saveOnlyFaceVideo = true
        }

        var interactionSteps: [any LCStepConfigProtocol] = []

        if (configuration.interactionEnabled){
            let blink = LCBlinkConfig()
            blink.timeout = configuration.interactionsConfig.interactionTimeout
            let leftHeadTrack = LCLeftHeadTrackConfig()
            leftHeadTrack.timeout = configuration.interactionsConfig.interactionTimeout
            leftHeadTrack.startAngle = 15.0
            leftHeadTrack.finishAngle = 20.0
            let rightHeadTrack = LCRightHeadTrackConfig()
            rightHeadTrack.timeout = configuration.interactionsConfig.interactionTimeout
            rightHeadTrack.startAngle = -15.0
            rightHeadTrack.finishAngle = -20.0

            interactionSteps.append(blink)
            let randomHeadTrack: any LCStepConfigProtocol = Bool.random() ? leftHeadTrack : rightHeadTrack
            interactionSteps.append(randomHeadTrack)
        }

        DispatchQueue.main.async {
            print("LMCameraBuilder.viewController START")
            self.viewController = LMCameraBuilder.viewController(
                delegate: self,
                configuration: self.configuration,
                livenessAPI: livenessAPI,
                canRecordVideo: self.recordVideo,
                interactionSteps: interactionSteps
            )

            self.viewController.dismissHandler = { [weak self] in guard let camera = self?.viewController else {
                return
            }
                camera.dismiss(animated: true)
            }
            self.bridge?.viewController?.present(self.viewController, animated: true)
        }
    }

    public func bestShot(_ bestShot: LCBestShotModel, _ videoFile: String?) {
        let image: UIImage = bestShot.getUIImageWarped(false)
        let imageData:Data = image.jpegData(compressionQuality: 1)!

        let base64Img = imageData.base64EncodedString(options: .lineLength64Characters)
        print("videoFile: \(videoFile ?? "")")
        self.call.resolve(["base64String": base64Img, "videoPath": videoFile ?? ""]);
        releaseCall()
        DispatchQueue.main.async {
            self.bridge?.viewController?.dismiss(animated: true)
        }
    }

    public func multipartBestShots(_ bestShots: [LCBestShotModel], _ videoFile: String?) {
        print("bestShots: \(bestShots)")
        print("videoFile: \(videoFile ?? "")")
    }

    public func interactionsStateUpdate(interactionsType: LCInteractionsType, stateUpdate: LCStepState) {
        print("interactionsStateUpdate: \(interactionsType) - \(stateUpdate)")
    }


    public func error(_ error: LMCameraError, _ videoFile: String?) {
        DispatchQueue.main.async {
            if case LMCameraError.accessStatus = error {
                self.call.reject(error.errorDescription ?? "Unknown error", "ERROR")
            } else {
                switch error {
                    case .canceled:
                    self.call.reject(error.errorDescription ?? "Unknown error", "CANCELLED")
                    case .error(let innerError):
                    guard let networkError = innerError as? LunaWeb.NetworkingError else {
                    self.call.reject(error.errorDescription ?? "Unknown error", "ERROR")
                    return
                }

                    switch networkError {
                        case .livenessPredictionFakeError:
                        self.call.reject(error.errorDescription ?? "Unknown error", "ERROR")
                        default:
                        self.call.reject(error.errorDescription ?? "Unknown error", "ERROR")
                    }

                    default:
                        self.call.reject(error.errorDescription ?? "Unknown error", "ERROR")
                }
            }
            self.bridge?.viewController?.dismiss(animated: true)
        }
    }

    public func deviceIsJailbroken() {
        DispatchQueue.main.async {
            self.bridge?.viewController?.dismiss(animated: true)
            self.call.reject("Device is jailbroken", "ERROR")
            self.releaseCall()
        }
    }

    public func shouldDeleteResultingVideo() -> Bool {
        return false
    }

    public func videoSaveProcessBegan() {

    }

    public func videoSavedWithError(_ error: (any Error)?) {

    }

    public func videoStopWithError(_ error: any Error) {

    }

    private func checkAndResetLicenseCache() {
        let currentAppVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
        let savedAppVersion = UserDefaults.standard.string(forKey: "AppVersion")

        if currentAppVersion != savedAppVersion {
            LCLunaIDServiceBuilder.resetLicenseCache()
            UserDefaults.standard.set(currentAppVersion, forKey: "AppVersion")
        }
    }

    private func checkLicenseFileExistence() -> Bool {
        let fileManager = FileManager.default
        let appFolderURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
        let licenseFileURL = appFolderURL.appendingPathComponent("license.dat")

        return fileManager.fileExists(atPath: licenseFileURL.path)
    }
}
