//
// Copyright 2023 Wultra s.r.o.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions
// and limitations under the License.
//

import AppProtection
import React

extension AppProtectionConfig {
    
    /// Create AppProtectionConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary) throws -> AppProtectionConfig {
        // Prepare online configuration depending on presence of "apple.service"
        let serviceDict = try dict.dictOptAt("apple.service")
        let onlineConfig: AppProtectionOnlineConfig?
        if let serviceDict = serviceDict {
            onlineConfig = .init(
                username: try dict.stringAt("apple.service.username"),
                password: try dict.stringAt("apple.service.password"),
                signaturePublicKey: try dict.stringOptAt("apple.service.signaturePublicKey"),
                clientIdentification: try .fromDictionary(dict.dictOptAt("clientIdentification")),
                eventsConfig: try .fromDictionary(dict.dictOptAt("apple.events")),
                customerGroupingConfig: try .fromDictionary(dict.dictOptAt("apple.customerGrouping")),
                environment: try .fromValue(dict.stringOptAt("apple.service.environment", in: "MalwarelyticsServiceEnvironment") ?? dict.stringOptAt("environment"))
            )
        } else {
            onlineConfig = nil
        }
        return AppProtectionConfig(
            raspConfig: try .fromDictionary(dict.dictOptAt("apple.rasp")),
            onlineConfig: onlineConfig
        )
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsConfig"
}

extension AppProtectionIdentificationConfig {
    
    /// Create AppProtectionIdentificationConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionIdentificationConfig? {
        guard let dict = dict else { return nil }
        return .init(
            userId: try dict.stringOptAt("clientId", in: jsObj),
            deviceId: try dict.stringOptAt("deviceId", in: jsObj)
        )
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsClientIdentification"
}

extension AppProtectionCustomerGroupingConfig {
    /// Create AppProtectionCustomerGroupingConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionCustomerGroupingConfig? {
        guard let dict = dict else { return nil }
        return .init(
            sourceBundleId: try dict.stringOptAt("sourceBundleId", in: jsObj),
            appBundleId: try dict.stringOptAt("appBundleId", in: jsObj),
            audienceGroupId: try dict.stringOptAt("audienceGroupId", in: jsObj)
        )
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleCustomerGroupingConfig"
}

extension AppProtectionRaspConfig {
    /// Create AppProtectionRaspConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig {
        let fallback = AppProtectionRaspConfig()
        guard let dict = dict else { return fallback }
        return .init(
            jailbreak: try .fromDictionary(dict.dictOptAt("jailbreak")) ?? fallback.jailbreak,
            debugger: try .fromDictionary(dict.dictOptAt("debugger")) ?? fallback.debugger,
            reverseEngineeringTools: try .fromDictionary(dict.dictOptAt("reverseEngineeringTools")) ?? fallback.reverseEngineeringTools,
            httpProxy: try .fromDictionary(dict.dictOptAt("httpProxy")) ?? fallback.httpProxy,
            repackage: try .fromDictionary(dict.dictOptAt("repackage")) ?? fallback.repackage,
            screenCapture: try .fromDictionary(dict.dictOptAt("screenCapture")) ?? fallback.screenCapture,
            vpnDetection: try .fromDictionary(dict.dictOptAt("vpn")) ?? fallback.vpnDetection,
            callDetection: try .fromDictionary(dict.dictOptAt("call")) ?? fallback.callDetection,
            appPresence: try .fromDictionary(dict.dictOptAt("appPresence")) ?? fallback.appPresence
        )
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleRaspConfig"
}

extension AppProtectionRaspConfig.DetectionConfig {
    /// Create AppProtectionRaspConfig.DetectionConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.DetectionConfig? {
        guard let dict = dict else {
            return nil
        }
        let action = try dict.stringAt("action", in: jsObj)
        let exitUrl = try dict.stringOptAt("exitUrl", in: jsObj)
        switch action {
        case "NO_ACTION":
            return .noAction
        case "NOTIFY":
            return .notify
        case "EXIT":
            return .exit(exitUrl)
        default:
            throw ModuleError.invalidConfig(configPath: "action", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleBasicDetectionConfig"
}

extension AppProtectionRaspConfig.DebuggerDetectionConfig {
    /// Create AppProtectionRaspConfig.DebuggerDetectionConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.DebuggerDetectionConfig? {
        guard let dict = dict else {
            return nil
        }
        let action = try dict.stringAt("action", in: jsObj)
        let exitUrl = try dict.stringOptAt("exitUrl", in: jsObj)
        switch action {
        case "NO_ACTION":
            return .noAction
        case "NOTIFY":
            return .notify
        case "BLOCK":
            return .block
        case "EXIT":
            return .exit(exitUrl)
        default:
            throw ModuleError.invalidConfig(configPath: "action", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleDebuggerDetectionConfig"
}

extension AppProtectionRaspConfig.RepackageConfig {
    /// Create AppProtectionRaspConfig.RepackageConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.RepackageConfig? {
        guard let dict = dict else {
            return nil
        }
        let action = try dict.stringAt("action", in: jsObj)
        let exitUrl = try dict.stringOptAt("exitUrl", in: jsObj)
        let certs = try dict.stringArrayAt("trustedCertificates", in: jsObj)
        let trustedCerts = try certs.map {
            guard let tc = AppProtectionRaspConfig.RepackageConfig.TrustedCertificate(withBase64EncodedString: $0) else {
                throw ModuleError.invalidConfig(configPath: "trustedCertificates", object: jsObj)
            }
            return tc
        }
        
        switch action {
        case "NO_ACTION":
            return .noAction(trustedCerts)
        case "NOTIFY":
            return .notify(trustedCerts)
        case "EXIT":
            return .exit(trustedCerts, exitUrl)
        default:
            throw ModuleError.invalidConfig(configPath: "action", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleRepackagingDetectionConfig"
}

extension AppProtectionRaspConfig.ScreenCaptureDetectionConfig {
    /// Create AppProtectionRaspConfig.ScreenCaptureDetectionConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.ScreenCaptureDetectionConfig? {
        guard let dict = dict else {
            return nil
        }
        let action = try dict.stringAt("action", in: jsObj)
        let exitUrl = try dict.stringOptAt("exitUrl", in: jsObj)
        let overlay: AppProtectionRaspConfig.ScreenCaptureDetectionConfig.Overlay? = try .fromDictionary(dict.dictOptAt("overlay", in: jsObj))
        switch action {
        case "NO_ACTION":
            return .noAction
        case "NOTIFY":
            return .notify
        case "HIDE":
            return .hide(overlay ?? AppProtectionRaspConfig.ScreenCaptureDetectionConfig.Overlay.default)
        case "EXIT":
            return .exit(exitUrl)
        default:
            throw ModuleError.invalidConfig(configPath: "action", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleScreenCaptureDetectionConfig"
}

extension AppProtectionRaspConfig.ScreenCaptureDetectionConfig.Overlay {
    /// Create AppProtectionRaspConfig.ScreenCaptureDetectionConfig.Overlay from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.ScreenCaptureDetectionConfig.Overlay? {
         guard let dict = dict else {
            return nil
        }
        let type = try dict.stringAt("type", in: jsObj)
        let color: UIColor? = try .fromDictionary(dict.dictOptAt("color", in: jsObj))
        let image: UIImage? = try .fromDictionary(dict.dictOptAt("image", in: jsObj))
        switch type {
        case "DEFAULT":
            return .`default`
        case "COLOR":
            guard let color = color else {
                throw ModuleError.invalidConfig(configPath: "color", object: jsObj)
            }
            return .color(color)
        case "IMAGE":
            guard let image = image else {
                throw ModuleError.invalidConfig(configPath: "image", object: jsObj)
            }
            return .image(image)
        default:
            throw ModuleError.invalidConfig(configPath: "type", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleOverlay"
}

extension UIColor {
    /// Create UIColor from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> UIColor? {
         guard let dict = dict else {
            return nil
        }
        let red = try dict.intAt("red", in: jsObj)
        let green = try dict.intAt("green", in: jsObj)
        let blue = try dict.intAt("blue", in: jsObj)
        let alpha = try dict.intAt("alpha", in: jsObj)
        return UIColor(
            red: CGFloat(Double(red)/255.0),
            green: CGFloat(Double(green)/255.0), 
            blue: CGFloat(Double(blue)/255.0), 
            alpha: CGFloat(Double(alpha)/255.0)
        )
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleColor"
}

extension UIImage {
    /// Create UIImage from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> UIImage? {
         guard let dict = dict else {
            return nil
        }
        let name = try dict.stringAt("name", in: jsObj)
        return UIImage(imageLiteralResourceName: name)
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleImage"
}

extension AppProtectionRaspConfig.SimpleDetectionConfig {
    /// Create AppProtectionRaspConfig.SimpleDetectionConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.SimpleDetectionConfig? {
        guard let dict = dict else {
            return nil
        }
        let action = try dict.stringAt("action", in: jsObj)
        switch action {
        case "NO_ACTION":
            return .noAction
        case "NOTIFY":
            return .notify
        default:
            throw ModuleError.invalidConfig(configPath: "action", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleSimpleDetectionConfig"
}

extension AppProtectionRaspConfig.AppPresenceDetectionConfig {
    /// Create AppProtectionRaspConfig.AppPresenceDetectionConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionRaspConfig.AppPresenceDetectionConfig? {
        guard let dict = dict else {
            return nil
        }
        let action = try dict.stringAt("action", in: jsObj)
        let srcApps = try dict.dictArrayAt("apps", in: jsObj)
        let apps = try srcApps.map {
            let dlProtocols = try $0.stringArrayAt("deeplinkProtocols", in: jsObj)
            let name = try $0.stringAt("name", in: jsObj)
            let cat = try $0.stringAt("category", in: jsObj)
            let category = switch cat {
                case "REMOTE_DESKTOP": DetectableApp.Category.remoteDesktop
                default: 
                    throw ModuleError.invalidConfig(configPath: "category", object: jsObj)
            }
            let tag = try $0.stringAt("tag", in: jsObj)
            return DetectableApp(
                deeplinkProtocols: dlProtocols, 
                name: name, 
                category: category, 
                tag: tag
            )
        }

        switch action {
        case "MANUAL":
            return .manual(apps)
        case "NOTIFY":
            return .notify(apps)
        default:
            throw ModuleError.invalidConfig(configPath: "action", object: jsObj)
        }
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleAppPresenceDetectionConfig"
}

extension AppProtectionEventConfig {
    /// Create AppProtectionEventConfig from JSON dictionary.
    /// - Parameter dict: Dictionary with configuration.
    /// - Returns: Configuration created from JSON dictionary.
    static func fromDictionary(_ dict: ConfigDictionary?) throws -> AppProtectionEventConfig {
        guard let dict = dict else { return .init() }
        return .init(
            enableEventCollection: try dict.boolAt("enableEventCollection", in: jsObj, fallback: true),
            enableScreenshotTakenCollection: try dict.boolAt("enableScreenshotTakenCollection", in: jsObj, fallback: true)
        )
    }
    
    /// Name of configuration object repodted in the exception.
    static let jsObj = "MalwarelyticsAppleEventsConfig"
}

extension AppProtectionEnvironment {
    /// Convert string into AppProtectionEnvironment enumeration.
    /// - Parameter value: String with environment specification.
    /// - Returns: Environment enumeration created from the string.
    static func fromValue(_ value: String?) throws -> AppProtectionEnvironment {
        guard let value = value else { return .production }
        switch value {
        case "PRODUCTION": return .production
        case "TEST": return .test
        default: throw ModuleError.invalidConfig(configPath: "environment")
        }
    }
}
