import ExpoModulesCore
@preconcurrency import OILiveness3D
import OIComponents
import OICommons
import AVFoundation
import OISecurity
import UIKit

public class OitiExpoLiveness3dModule: Module, Liveness3DDelegate {
    
    var livenessContinuation: CheckedContinuation<[String: Any], Error>? = nil
    
    public func definition() -> ModuleDefinition {
        
        Name("OitiExpoLiveness3d")
        
        Constants([
          "PI": Double.pi
        ])
        
        Events("onChange", "onLoad")
        
        Function("hello") {
          return "Hello world! 👋"
        }
        
        AsyncFunction("setValueAsync") { (value: String) in
          self.sendEvent("onChange", [
            "value": value
          ])
        }
        
        AsyncFunction("askCameraPermission") { (args: [String: Any]) async throws -> Bool in
            return await withCheckedContinuation { continuation in
                AVCaptureDevice.requestAccess(for: .video) { granted in
                    DispatchQueue.main.async {
                        if granted {
                            continuation.resume(returning: true)
                        } else {
                            if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
                                UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
                            }
                            continuation.resume(returning: false)
                        }
                    }
                }
            }
        }
        
        Function("checkCameraPermission") { (args: [String: Any]) -> Bool in
            return AVCaptureDevice.authorizationStatus(for: .video) == AVAuthorizationStatus.authorized
        }
        
        AsyncFunction("startliveness3d") { (args: [String: Any]) async throws -> [String: Any] in
            let appKey = args["appkey"] as? String ?? ""
            let ticket = args["ticket"] as? String
            let env = (args["environment"] as? String)?.uppercased() ?? "HML"
            let loading = args["loading"] as? [String: Any]
            let typeLoading = loading?["type"] as? String ?? "default"
            let sizeLoading = self.parseLoadingSize(loading?["size"]) ?? 10
            let backgroundColor = loading?["backgroundColor"] as? String ?? "#FFFFFF"
            let loadingColor = loading?["loadingColor"] as? String ?? "#000000"
            
            let texts = self.liveness3DTexts(from: args["liveness3Dtext"])
            let theme = self.liveness3DTheme(theme: args["theme"], fonts: args["fonts"])
            
            let environmentValue = env == "PRD" ? Environment.PRD : Environment.HML
            
            let liveness3DUser = Liveness3DUser(
                appKey: appKey,
                ticket: ticket,
                environment: environmentValue,
                defaultTheme: theme,
                lowLightTheme: theme,
                texts: texts
            )
            
            let hasAccess = await withCheckedContinuation { continuation in
                AVCaptureDevice.requestAccess(for: .video) { granted in
                    continuation.resume(returning: granted)
                }
            }
            
            if !hasAccess {
                throw NSError(domain: "OitiExpoLiveness3d", code: -1, userInfo: [NSLocalizedDescriptionKey: "Camera access not granted"])
            }
            
            let spinnerLoading = SpinnerConfiguration(
                backgroundColor: UIColor(hex: backgroundColor),
                loadingColor: UIColor(hex: loadingColor),
                strokeWidth: 10,
                scaleFactor: Int(CGFloat(sizeLoading))
            )
            
            let defaultLoading = ActivityIndicatorConfiguration(
                loadingColor: UIColor(hex: loadingColor),
                backgroundColor: UIColor(hex: backgroundColor),
                scaleFactor: Int(CGFloat(sizeLoading))
            )
            
            let customAppearance: HybridViewAppearance = typeLoading == "spinner"
                ? .init(configuration: spinnerLoading)
                : .init(configuration: defaultLoading)
            
            return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[String: Any], Error>) in
                self.livenessContinuation = continuation
                
                DispatchQueue.main.async {
                    let liveness3DViewController = HybridLiveness3DViewController(
                        liveness3DUser: liveness3DUser,
                        delegate: self,
                        customAppearance: customAppearance
                    )
                    liveness3DViewController.modalPresentationStyle = .fullScreen
                    
                    guard let presenter = self.appContext?.utilities?.currentViewController() else {
                        self.livenessContinuation?.resume(throwing: NSError(
                            domain: "OitiExpoLiveness3d",
                            code: -1,
                            userInfo: [NSLocalizedDescriptionKey: "No view controller available to present Liveness3D"]
                        ))
                        self.livenessContinuation = nil
                        return
                    }
                    presenter.present(liveness3DViewController, animated: true)
                }
            }
        }
       
        View(OitiExpoLiveness3dView.self) {
          Prop("url") { (view: OitiExpoLiveness3dView, url: URL) in
            if view.webView.url != url {
              view.webView.load(URLRequest(url: url))
            }
          }
          Events("onLoad")
        }
    }
    
    public func handleLiveness3DValidation(validateModel: Liveness3DSuccess) {
        let response: [String: Any] = [
            "cause": validateModel.cause ?? "",
            "codId": String(validateModel.codID ?? 0.0),
            "protocol": validateModel.protocolo ?? "",
            "blob": validateModel.scanResultBlob ?? "",
            "valid": validateModel.valid ?? false
        ]
        self.livenessContinuation?.resume(returning: response)
        self.livenessContinuation = nil
    }
    
    public func handleLiveness3DError(error: OILiveness3D.Liveness3DError) {
        self.livenessContinuation?.resume(throwing: NSError(domain: "OitiExpoLiveness3d", code: error.code, userInfo: [NSLocalizedDescriptionKey: error.message]))
        self.livenessContinuation = nil
    }
    
    func textKey(from identifier: String) -> Liveness3DTextKey? {
        switch identifier {
        case "READY_HEADER_1": return .readyHeader1
        case "READY_HEADER_2": return .readyHeader2
        case "READY_MESSAGE_1": return .readyMessage1
        case "READY_MESSAGE_2": return .readyMessage2
        case "READY_BUTTON": return .readyButton
        case "RETRY_HEADER": return .retryHeader
        case "RETRY_SUBHEADER": return .retrySubheader
        case "RETRY_YOUR_PICTURE": return .retryYourPicture
        case "RETRY_IDEAL_PICTURE": return .retryIdealPicture
        case "RETRY_MESSAGE_SMILE": return .retryMessageSmile
        case "RETRY_MESSAGE_LIGHTING": return .retryMessageLightning
        case "RETRY_MESSAGE_CONTRAST": return .retryMessageContrast
        case "RETRY_BUTTON": return .retryButton
        case "RESULT_UPLOAD_MESSAGE": return .resultUploadMessage
        case "RESULT_SUCCESS_MESSAGE": return .resultSuccessMessage
        case "FEEDBACK_CENTER_FACE": return .feedbackCenterFace
        case "FEEDBACK_FACE_NOT_FOUND": return .feedbackFaceNotFound
        case "FEEDBACK_FACE_NOT_LOOKING_STRAIGHT_AHEAD": return .feedbackFaceNotLookingStraightAhead
        case "FEEDBACK_FACE_NOT_UPRIGHT": return .feedbackFaceNotUpright
        case "FEEDBACK_HOLD_STEADY": return .feedbackHoldSteady
        case "FEEDBACK_HOLD_STEADY_1": return .feedbackHoldSteady1
        case "FEEDBACK_HOLD_STEADY_2": return .feedbackHoldSteady2
        case "FEEDBACK_HOLD_STEADY_3": return .feedbackHoldSteady3
        case "FEEDBACK_MOVE_PHONE_AWAY": return .feedbackMovePhoneAway
        case "FEEDBACK_MOVE_PHONE_CLOSER": return .feedbackMovePhoneCloser
        case "FEEDBACK_MOVE_PHONE_TO_EYE_LEVEL": return .feedbackMovePhoneToEyeLevel
        case "FEEDBACK_USE_EVEN_LIGHTING": return .feedbackUseEvenLighting
        case "FEEDBACK_FRAME_YOUR_FACE": return .feedbackFrameYourFace
        case "FEEDBACK_POSITION_FACE_STRAIGHT_IN_OVAL": return .feedbackPositionFaceStraightInOval
        case "FEEDBACK_REMOVE_DARK_GLASSES": return .feedbackRemoveDarkGlasses
        case "FEEDBACK_NEUTRAL_EXPRESSION": return .feedbackNeutralExpression
        case "FEEDBACK_CONDITIONS_TOO_BRIGHT": return .feedbackConditionsTooBright
        case "FEEDBACK_BRIGHTEN_YOUR_ENVIRONMENT": return .feedbackBrightenYourEnvironment
        default: return nil
        }
    }
    
    func liveness3DTexts(from arguments: Any?) -> [Liveness3DTextKey: String] {
        guard let textsDictionary = arguments as? [String: String] else {
            return [:]
        }
        
        let sequence = textsDictionary.compactMap { key, value -> (Liveness3DTextKey, String)? in
            guard let livenessKey = self.textKey(from: key), !value.isEmpty else {
                return nil
            }
            return (livenessKey, value)
        }
        return Dictionary(uniqueKeysWithValues: sequence)
    }

    private func parseLoadingSize(_ value: Any?) -> Int? {
        switch value {
        case let intValue as Int:
            return intValue
        case let doubleValue as Double:
            return Int(doubleValue)
        case let numberValue as NSNumber:
            return numberValue.intValue
        case let stringValue as String:
            return Int(stringValue)
        default:
            return nil
        }
    }
    
    func liveness3DTheme(theme: Any?, fonts: Any?) -> Liveness3DTheme {
        let themeDictionary = theme as? [String: Any]
        let fontsDictionary = fonts as? [String: Any]
        
        var themeInstance = Liveness3DTheme(.light)
        let hasThemeCustomizations = !(themeDictionary?.isEmpty ?? true)
        let hasFontCustomizations = !(fontsDictionary?.isEmpty ?? true)
        if !hasThemeCustomizations && !hasFontCustomizations {
            return themeInstance
        }
        
        func themeString(_ key: String, legacyKey: String? = nil) -> String? {
            let primary = (themeDictionary?[key] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
            if let primary, !primary.isEmpty { return primary }
            guard let legacyKey else { return nil }
            let legacy = (themeDictionary?[legacyKey] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
            if let legacy, !legacy.isEmpty { return legacy }
            return nil
        }
        
        func fontName(_ key: String) -> String? {
            let name = (fontsDictionary?[key] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
            guard let name, !name.isEmpty else { return nil }
            return name
        }
        
        func int32Value(_ key: String, legacyKey: String? = nil) -> Int32? {
            let value = themeDictionary?[key] ?? (legacyKey != nil ? themeDictionary?[legacyKey!] : nil)
            switch value {
            case let intValue as Int: return Int32(intValue)
            case let doubleValue as Double: return Int32(doubleValue)
            case let numberValue as NSNumber: return numberValue.int32Value
            case let stringValue as String: return Int32(stringValue)
            default: return nil
            }
        }
        
        if let name = fontName("readyScreenCustomizationHeaderFont") {
            themeInstance.readyScreenCustomizationHeaderFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("readyScreenCustomizationSubtextFont") {
            themeInstance.readyScreenCustomizationSubtextFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("retryScreenCustomizationHeaderFont") {
            themeInstance.retryScreenCustomizationHeaderFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("retryScreenCustomizationSubtextFont") {
            themeInstance.retryScreenCustomizationSubtextFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("resultScreenCustomizationMessageFont") {
            themeInstance.resultScreenCustomizationMessageFont = UIFont(name: name, size: 15)
        }
        if let name = fontName("guidanceCustomizationHeaderFont") {
            themeInstance.guidanceCustomizationHeaderFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("guidanceCustomizationSubtextFont") {
            themeInstance.guidanceCustomizationSubtextFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("guidanceCustomizationButtonFont") {
            themeInstance.guidanceCustomizationButtonFont = UIFont(name: name, size: 14)
        }
        if let name = fontName("feedbackCustomizationTextFont") {
            themeInstance.feedbackCustomizationTextFont = UIFont(name: name, size: 14)
        }
        
        if let hex = themeString("guidanceCustomizationReadyScreenHeaderTextColor") { themeInstance.readyScreenCustomizationHeaderTextColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationReadyScreenSubtextTextColor") { themeInstance.readyScreenCustomizationSubtextTextColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationReadyScreenTextBackgroundColor", legacyKey: "guidanceCustomizationTextBackgroundColor") { themeInstance.readyScreenCustomizationTextBackgroundColor = UIColor(hex: hex) }
        if let value = int32Value("guidanceCustomizationReadyScreenTextBackgroundCornerRadius", legacyKey: "guidanceCustomizationTextBackgroundColorRadius") { themeInstance.readyScreenCustomizationTextBackgroundCornerRadius = value }
        
        if let hex = themeString("guidanceCustomizationRetryScreenHeaderTextColor") { themeInstance.retryScreenCustomizationHeaderTextColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationRetryScreenSubtextTextColor") { themeInstance.retryScreenCustomizationSubtextTextColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationRetryScreenImageBorderColor") { themeInstance.retryScreenCustomizationImageBorderColor = UIColor(hex: hex) }
        if let value = int32Value("guidanceCustomizationRetryScreenImageBorderWidth") { themeInstance.retryScreenCustomizationImageBorderWidth = value }
        if let value = int32Value("guidanceCustomizationRetryScreenImageCornerRadius") { themeInstance.retryScreenCustomizationImageCornerRadius = value }
        
        if let hex = themeString("resultScreenCustomizationForegroundColor", legacyKey: "resultScreenCustomizationTextColor") { themeInstance.resultScreenCustomizationTextColor = UIColor(hex: hex) }
        if let hex = themeString("resultScreenCustomizationUploadProgressFillColor") { themeInstance.resultScreenCustomizationUploadProgressFillColor = UIColor(hex: hex) }
        if let hex = themeString("resultScreenCustomizationUploadProgressTrackColor") { themeInstance.resultScreenCustomizationUploadProgressTrackColor = UIColor(hex: hex) }
        
        let blobColorHex = themeString("resultScreenCustomizationActivityIndicatorColor")
        let checkmarkFgHex = themeString("resultScreenCustomizationResultAnimationForegroundColor")
        let checkmarkBgHex = themeString("resultScreenCustomizationResultAnimationBackgroundColor")
        if let blobColorHex, let checkmarkFgHex, let checkmarkBgHex {
            themeInstance.resultScreenCustomizationAnimationStyle = .blob(appearance: BlobAnimationAppearance(
                blobColor: UIColor(hex: blobColorHex),
                checkmarkForegroundColor: UIColor(hex: checkmarkFgHex),
                checkmarkBackgroundColor: UIColor(hex: checkmarkBgHex)
            ))
        }
        
        if let hex = themeString("guidanceCustomizationButtonTextNormalColor") { themeInstance.guidanceCustomizationButtonTextNormalColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationButtonBackgroundNormalColor") { themeInstance.guidanceCustomizationButtonBackgroundNormalColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationButtonTextHighlightColor") { themeInstance.guidanceCustomizationButtonTextHighlightColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationButtonBackgroundHighlightColor") { themeInstance.guidanceCustomizationButtonBackgroundHighlightColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationButtonTextDisabledColor") { themeInstance.guidanceCustomizationButtonTextDisabledColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationButtonBackgroundDisabledColor") { themeInstance.guidanceCustomizationButtonBackgroundDisabledColor = UIColor(hex: hex) }
        if let hex = themeString("guidanceCustomizationButtonBorderColor") { themeInstance.guidanceCustomizationButtonBorderColor = UIColor(hex: hex) }
        if let value = int32Value("guidanceCustomizationButtonBorderWidth") { themeInstance.guidanceCustomizationButtonBorderWidth = value }
        if let value = int32Value("guidanceCustomizationButtonCornerRadius") { themeInstance.guidanceCustomizationButtonCornerRadius = value }
        
        if let value = int32Value("frameCustomizationBorderWidth") { themeInstance.frameCustomizationBorderWidth = value }
        if let value = int32Value("frameCustomizationCornerRadius") { themeInstance.frameCustomizationCornerRadius = value }
        if let hex = themeString("frameCustomizationBorderColor") { themeInstance.frameCustomizationBorderColor = UIColor(hex: hex) }
        if let hex = themeString("frameCustomizationBackgroundColor") { themeInstance.frameCustomizationBackgroundColor = UIColor(hex: hex) }
        
        if let value = int32Value("ovalCustomizationStrokeWidth") { themeInstance.ovalCustomizationStrokeWidth = value }
        if let hex = themeString("ovalCustomizationStrokeColor") { themeInstance.ovalCustomizationStrokeColor = UIColor(hex: hex) }
        if let value = int32Value("ovalCustomizationProgressStrokeWidth") { themeInstance.ovalCustomizationProgressStrokeWidth = value }
        if let hex = themeString("ovalCustomizationProgressColor1") { themeInstance.ovalCustomizationProgressColor1 = UIColor(hex: hex) }
        if let hex = themeString("ovalCustomizationProgressColor2") { themeInstance.ovalCustomizationProgressColor2 = UIColor(hex: hex) }
        if let value = int32Value("ovalCustomizationProgressRadialOffset") { themeInstance.ovalCustomizationProgressRadialOffset = value }
        
        if let hex = themeString("overlayCustomizationBackgroundColor") { themeInstance.overlayCustomizationBackgroundColor = UIColor(hex: hex) }
        if let hex = themeString("feedbackCustomizationTextColor") { themeInstance.feedbackCustomizationTextColor = UIColor(hex: hex) }
        if let value = int32Value("feedbackCustomizationCornerRadius") { themeInstance.feedbackCustomizationCornerRadius = value }
        if let hex = themeString("feedbackCustomizationBackgroundColors") { themeInstance.feedbackCustomizationBackgroundColor = UIColor(hex: hex) }
        
        return themeInstance
    }
}

public extension UIColor {
    convenience init(hex: String) {
        var cString: String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()

        if cString.hasPrefix("#") {
            cString.remove(at: cString.startIndex)
        }

        if cString.count != 6 {
            self.init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
            return
        }

        var rgbValue: UInt64 = 0
        Scanner(string: cString).scanHexInt64(&rgbValue)

        self.init(
            red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: 1.0
        )
    }
}
