//
// 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

extension Malwarelytics: AppProtectionRaspDelegate {
            
    func debuggerDetected() {
        sendRaspEvent(RASPMessage(type: "DEBUGGER", payload: true))
    }
    
    func jailbreakDetected() {
        sendRaspEvent(RASPMessage(type: "SYSTEM_INTEGRITY", payload: SystemIntegrityInfo(isJailbroken: true, isRooted: false)))
    }
    
    func repackageDetected() {
        sendRaspEvent(RASPMessage(type: "REPACKAGED", payload: "REPACKAGED_APP"))
    }
    
    func httpProxyEnabled() {
        sendRaspEvent(RASPMessage(type: "HTTP_PROXY", payload: HttpProxyInfo(isHttpProxyEnabled: true)))
    }
    
    func userScreenshotDetected() {
        sendRaspEvent(RASPMessage(type: "SCREENSHOT", payload: ""))
    }
    
    func screenCapturedChanged(isCaptured: Bool) {
        sendRaspEvent(RASPMessage(type: "SCREEN_SHARING", payload: ScreenSharingInfo(isScreenShared: isCaptured)))
    }
    
    func reverseEngineeringToolsDetected() {
        sendRaspEvent(RASPMessage(type: "REVERSE_TOOLS", payload: ""))
    }
    
    func systemPasscodeConfigurationChanged(enabled: Bool) {
        sendRaspEvent(RASPMessage(type: "DEVICE_PASSCODE", payload: enabled))
    }
    
    func systemBiometryConfigurationChanged(enabled: Bool) {
        sendRaspEvent(RASPMessage(type: "DEVICE_BIOMETRY", payload: enabled))
    }
    
    func vpnChanged(active: Bool) {
        sendRaspEvent(RASPMessage(type: "VPN", payload: active))
    }
    
    func onCallChanged(isOnCall: Bool) {
        sendRaspEvent(RASPMessage(type: "ON_CALL", payload: isOnCall))
    }

    func installedAppsChanged(installedApps: [DetectableApp]) {
        sendRaspEvent(RASPMessage(type: "APP_PRESENCE", 
            payload: AppPresenceInfo(
                appleAppPresenceInfo: AppleAppPresenceInfo(installedApps: installedApps)
            )
        ))
    }
    
    /// Send RASP event to JavaScript.
    /// - Parameter payload: Message body.
    private func sendRaspEvent(_ payload: Encodable) {
        sendEvent(.rasp, body: payload)
    }
}

extension Malwarelytics {
    
    /// Retrieve RASP information from the service.
    /// - Parameters:
    ///   - service: AppProtectionService implementation.
    ///   - infoType: RASP information type.
    /// - Returns: Retrieved information.
    func getRaspInfo(service: AppProtectionService, infoType: String) throws -> Encodable {
        let rasp = service.rasp
        switch infoType {
        case "DEBUGGER":
            return rasp.isDebuggerConnected
        case "SYSTEM_INTEGRITY":
            return SystemIntegrityInfo(isJailbroken: rasp.isJailbroken, isRooted: false)
        case "REPACKAGED":
            return rasp.isRepackaged ? "REPACKAGED_APP" : "ORIGINAL_APP"
        case "HTTP_PROXY":
            return HttpProxyInfo(isHttpProxyEnabled: rasp.isHttpProxyEnabled)
        case "SCREEN_SHARING":
            return ScreenSharingInfo(isScreenShared: rasp.isScreenCaptured)
        case "REVERSE_TOOLS":
            return rasp.isReverseEngineeringToolsPresent
        case "DEVICE_PASSCODE":
            return rasp.isSystemPasscodeEnabled
        case "DEVICE_BIOMETRY":
            return rasp.isSystemBiometryEnabled
        case "EMULATOR":
            return EmulatorInfo(isEmulator: rasp.isEmulator, detectedEmulatorType: "APPLE")
        case "VPN":
            return rasp.isVpnActive
        case "ON_CALL":
            return rasp.isOnCall
        case "APP_PRESENCE":
            return AppPresenceInfo(
                appleAppPresenceInfo: AppleAppPresenceInfo(installedApps: rasp.installedApps)
            )
        default:
            throw ModuleError.invalidParam(paramName: "message")
        }
    }
    
    /// Called once, after the service is initialized.
    /// - Parameter service: Current service instance.
    func raspOnInit(service: AppProtectionService) {
        let rasp = service.rasp
        if rasp.isEmulator {
            // The "emulator" signal is not propagated to the listener, so we have to
            // emulate the Android behavior.
            sendRaspEvent(RASPMessage(type: "EMULATOR", payload: EmulatorInfo(isEmulator: true, detectedEmulatorType: "APPLE")))
        }
    }
}

/// Generic RASP message sent back to JavaScript in the event.
fileprivate struct RASPMessage<T: Encodable>: Encodable {
    let type: String
    let payload: T
}

fileprivate struct SystemIntegrityInfo: Encodable {
    let isJailbroken: Bool
    let isRooted: Bool
}

fileprivate struct HttpProxyInfo: Encodable {
    let isHttpProxyEnabled: Bool
}

fileprivate struct ScreenSharingInfo: Encodable {
    let isScreenShared: Bool
}

fileprivate struct EmulatorInfo: Encodable {
    let isEmulator: Bool
    let detectedEmulatorType: String
}

fileprivate struct AppPresenceInfo: Encodable {
    let appleAppPresenceInfo: AppleAppPresenceInfo?
}

fileprivate struct AppleAppPresenceInfo: Encodable {
    let installedApps: [DetectableApp]
}

extension DetectableApp : Encodable {
    enum CodingKeys: CodingKey {
        case deeplinkProtocols
        case name
        case category
        case tag
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(deeplinkProtocols, forKey: .deeplinkProtocols)
        try container.encode(name, forKey: .name)
        try container.encode(category, forKey: .category)
        try container.encode(tag, forKey: .tag)
    }
}

extension DetectableApp.Category : Encodable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
            case .remoteDesktop:
                try container.encode("REMOTE_DESKTOP")
        }
    }
}