//
// 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 type { EmitterSubscription } from "react-native";
import type { EmulatorInfo } from "./model/rasp/EmulatorInfo";
import type { HttpProxyInfo } from "./model/rasp/HttpProxyInfo";
import type { RepackagingInfo } from "./model/rasp/RepackageInfo";
import type { ScreenSharingInfo } from "./model/rasp/ScreenSharingInfo";
import type { ScreenReaderInfo } from "./model/rasp/ScreenReaderInfo";
import type { SystemIntegrityInfo } from "./model/rasp/SystemIntegrityInfo";
import type { TapjackingInfo } from "./model/rasp/TapjackingInfo";
import type { ActiveCallInfo } from "./model/rasp/ActiveCallInfo";
import type { AppPresenceInfo } from "./model/rasp/AppPresenceInfo";
import type { DebuggerInfo } from "./model/rasp/DebuggerInfo";
import type { BiometryInfo } from "./model/rasp/BiometryInfo";
import type { EventHelper } from "./internal/EventHelper";
import type { RaspEvent, RaspEventType } from "./internal/RaspEvent";
import { MalwarelyticsModuleIfc, wrapNativeCall } from "./internal/MalwarelyticsModule";
import { Platform } from "react-native";
import { MalwarelyticsError } from "./MalwarelyticsError";

/**
 * Malwarelytics RASP module.
 */
export class MalwarelyticsRasp {

    constructor(eventHelper: EventHelper) {
        this.eventHelper = eventHelper
        this.module = eventHelper.module
    }

    /**
     * Instance of EventHelper shared with Malwarelytics class.
     */
    private readonly eventHelper: EventHelper
    /**
     * Instance of native module interface.
     */
    private readonly module: MalwarelyticsModuleIfc

    /**
     * Object representing a subscription to RASP events.
     */
    private raspEventsSubscription: EmitterSubscription | undefined

    /**
     * Set listener for RASP events.
     * @param listener Listener implementation.
     */
    async setRaspListener(listener: MalwarelyticsRaspListener): Promise<void> {
        this.raspEventsSubscription?.remove()
        this.raspEventsSubscription = await this.eventHelper.addListener('Malwarelytics.RASP', (data) => {
            //console.log(`${Platform.OS}: RASP event: ${JSON.stringify(data)}`)
            const m = data as RaspEvent
            switch (m.type) {
                
                // Apple + Android
                
                case "DEBUGGER":
                    listener.debuggerDetected(m.payload as boolean);
                    break
                case "REPACKAGED":
                    listener.repackagingDetected(m.payload as RepackagingInfo)
                    break
                case "SYSTEM_INTEGRITY":
                    listener.systemIntegrityCompromised(m.payload as SystemIntegrityInfo)
                    break
                case "HTTP_PROXY":
                    listener.httpProxyDetected(m.payload as HttpProxyInfo)
                    break
                case "SCREEN_SHARING":
                    listener.screenSharingDetected(m.payload as ScreenSharingInfo)
                    break
                case "EMULATOR":
                    listener.emulatorDetected(m.payload as EmulatorInfo)
                    break
                case "VPN":
                    listener.vpnDetected(m.payload as boolean)
                    break
                case "APP_PRESENCE":
                    listener.appPresenceChangeDetected(m.payload as AppPresenceInfo)
                    break;
                
                
                // Android specific

                case "SCREEN_READER":
                    listener.screenReaderDetected(m.payload as ScreenReaderInfo)
                    break
                case "TAPJACKING":
                    listener.tapjackingDetected(m.payload as TapjackingInfo)
                    break
                case "ADB_STATUS":
                    listener.adbStatusDetected(m.payload as boolean)
                    break
                case "ACTIVE_CALL":
                    listener.activeCallDetected(m.payload as ActiveCallInfo)
                    break;
                
                
                // Apple specific

                case "SCREENSHOT":
                    listener.userScreenshotDetected()
                    break
                case "REVERSE_TOOLS":
                    listener.reverseEngineeringToolsDetected()
                    break
                case "DEVICE_PASSCODE":
                    listener.systemPasscodeConfigurationChanged(m.payload as boolean)
                    break
                case "DEVICE_BIOMETRY":
                    listener.systemBiometryConfigurationChanged(m.payload as boolean)
                    break
                case "ON_CALL":
                    listener.isOnCallChanged(m.payload as boolean)
                    break

                default:
                    console.warn(`${Platform.OS}: Unsupported RASP event ${m.type}`)
                    break
            }
        })
    }

    /**
     * Remove RASP listener previously set by `setRaspListener()` method.
     */
    removeRaspListener() {
        this.raspEventsSubscription?.remove()
        this.raspEventsSubscription = undefined
    }

    // Android + Apple

    /**
     * Get information about Jailbreak or Root presence on the device.
     */
    getSystemIntegrityInfo(): Promise<SystemIntegrityInfo> {
        return this.getRaspInfo("SYSTEM_INTEGRITY")
    }
    /**
     * Get information whether app is running in emulator. You can use `getEmulatorInfo()` method to get more details 
     * about the emulator type.
     */
    async isRunningInEmulator(): Promise<boolean> {
        return (await this.getEmulatorInfo()).isEmulator
    }
    /**
     * Get information whether debugger is connected.
     */
    isDebuggerConnected(): Promise<boolean> {
        return this.getRaspInfo("DEBUGGER")
    }
    /**
     * Get detailed information about debugger detection.
     */
    getDebuggerInfo(): Promise<DebuggerInfo> {
        return this.getRaspAndroidInfo("DEBUGGER_INFO")
    }
    /**
     * Get information about application repackaging.
     */
    getRepackagingInfo(): Promise<RepackagingInfo> {
        return this.getRaspInfo("REPACKAGED")
    }
    /**
     * Get information about HTTP proxy configured on the system.
     */
    getHttpProxyInfo(): Promise<HttpProxyInfo> {
        return this.getRaspInfo("HTTP_PROXY")
    }
    /**
     * Get information whether app is running in emulator.
     */
    getEmulatorInfo(): Promise<EmulatorInfo> {
        return this.getRaspInfo("EMULATOR")
    }
    /**
     * Get information about active screen sharing or screen capturing.
     */
    getScreenSharingInfo(): Promise<ScreenSharingInfo> {
        return this.getRaspInfo("SCREEN_SHARING")
    }
    /**
     * Get information about active VPN connection.
     */
    isVpnActive(): Promise<boolean> {
        return this.getRaspInfo("VPN")
    }
    /**
     * Get information about the active phone call.
     */
    isOnCall(): Promise<boolean> {
        return this.getRaspInfo("ON_CALL")
    }
    /**
     * Obtain information about app presence.
     */
    getAppPresenceInfo(): Promise<AppPresenceInfo> {
        return this.getRaspInfo("APP_PRESENCE")
    }

    // Apple specific

    /**
     * Apple specific: Get information whether reverse engineering tools are present on the device.
     */
    isReverseEngineeringToolsPresent(): Promise<boolean> {
        return this.getRaspAppleInfo("REVERSE_TOOLS")
    }
    /**
     * Apple specific: Get information about enabled passcode in the system (device lock)
     */
    isSystemPasscodeEnabled(): Promise<boolean> {
        return this.getRaspAppleInfo("DEVICE_PASSCODE")
    }
    /**
     * Apple specific: Get information about biometry enrolled by the user in the system.
     */
    isSystemBiometryEnabled(): Promise<boolean> {
        return this.getRaspAppleInfo("DEVICE_BIOMETRY")
    }

    // Android specific

    /**
     * Android specific: Get information about tapjacking.
     */
    getTapjackingInfo(): Promise<TapjackingInfo> {
        return this.getRaspAndroidInfo("TAPJACKING")
    }
    /**
     * Android specific: Get information about connected ADB.
     */
    getAdbStatus(): Promise<boolean> {
       return this.getRaspAndroidInfo("ADB_STATUS") 
    }

    /**
     * Android specific: Check if system screen lock (PIN or pattern) is being used to prevent
     * unauthorized usage of the device by other people. It does not check if the device is currently locked.
     */
    isScreenLockEnabled(): Promise<boolean> {
        return this.getRaspAndroidInfo("SCREEN_LOCK") 
    }

    /**
     * Android specific: Check if Play Protect is enabled on the device. `undefined` value indicates that there was 
     * a problem obtaining the information.
     */
    isPlayProtectEnabled(): Promise<boolean | undefined> {
        return this.getRaspAndroidInfo("PLAY_PROTECT") 
    }

    /**
     * Android specific: Get information about screen readers.
     */
    getScreenReaderInfo(): Promise<ScreenReaderInfo> {
        return this.getRaspAndroidInfo("SCREEN_READER")
    }

    /**
     * Android specific: Check if any not allowed screen reader is enabled on the device. Allowed screen readers are configured 
     * in `MalwarelyticsAndroidRaspScreenReadersConfig.allowedScreenReaders`.
     */
    isNotAllowedScreenReaderEnabled(): Promise<boolean> {
        return this.getRaspAndroidInfo("NA_SCREEN_READER") 
    }

    /**
     * Android specific: Check if there's a bad app that is able to create a system overlay. A bad app is one that 
     * has a treat index same or higher than `MalwarelyticsAndroidRaspTapjackingConfig.blockSensitivity`.
     */
    isBadTapjackingCapableAppPresent(): Promise<boolean> {
        return this.getRaspAndroidInfo("TAPJACKING_APP_PRESENT")
    }

    /**
     * Android specific: Check if developer options are enabled on the device.
     */
    isDeveloperOptionsEnabled(): Promise<boolean> {
        return this.getRaspAndroidInfo("DEVELOPER_MODE")
    }

    /**
     * Android specific: Obtain information about biometry on the device.
     */
    getBiometryInfo(): Promise<BiometryInfo> {
        return this.getRaspAndroidInfo("BIOMETRY")
    }

    /**
     * Android specific: Obtain information about active call.
     */
    getActiveCallInfo(): Promise<ActiveCallInfo> {
        return this.getRaspAndroidInfo("ACTIVE_CALL")
    }

    // Private methods

    /**
     * Acquire typed information about RASP detection.
     * @param messageType RASP message to get.
     * @returns Value returned from native code.
     */
    private async getRaspInfo<T>(messageType: RaspEventType): Promise<T> {
        return await wrapNativeCall(this.module, (module) => module.getRaspInfo(messageType)) as T
    }
    /**
     * Acquire typed information about RASP detection. This function fails if called on non-Apple platform.
     * @param messageType RASP message to get.
     * @returns Value returned from native code.
     */
    private getRaspAppleInfo<T>(messageType: RaspEventType): Promise<T> {
        if (Platform.OS != "ios") {
            return Promise.reject(new MalwarelyticsError("METHOD_NOT_SUPPORTED", "This method is supported only on Apple platforms"))
        }
        return this.getRaspInfo(messageType)
    }
    /**
     * Acquire typed information about RASP detection. This function fails if called on non-Android platform.
     * @param messageType RASP message to get.
     * @returns Value returned from native code.
     */
    private getRaspAndroidInfo<T>(messageType: RaspEventType): Promise<T> {
        if (Platform.OS != "android") {
            return Promise.reject(new MalwarelyticsError("METHOD_NOT_SUPPORTED", "This method is supported only on Android platform"))
        }
        return this.getRaspInfo(messageType)
    }
}

export interface MalwarelyticsRaspListener {

    // Android + Apple

    /**
     * Called when debuger is attached to the executable.
     * 
     * Platforms: Apple, Android
     * 
     * @param detected True is debugger is attached.
     */
    debuggerDetected(detected: boolean): void

    /**
     * Called when repackage is detected.
     * 
     * Platforms: Apple, Android
     * 
     * @param info Information about repackaging.
     */
    repackagingDetected(info: RepackagingInfo): void

    /**
     * Called when device is jailbroken or rooted.
     * 
     * Platforms: Apple, Android
     * 
     * @param info Information about compromited system integrity.
     */
    systemIntegrityCompromised(info: SystemIntegrityInfo): void

    /**
     * Called when application is running in emulator environment.
     * 
     * Platforms: Apple, Android
     * 
     * @param info Information about emulator environment.
     */
    emulatorDetected(info: EmulatorInfo): void

    /**
     * Called when HTTP proxy is detected.
     * 
     * Platforms: Apple (limited), Android
     * 
     * @param info Information about proxy configuration.
     */
    httpProxyDetected(info: HttpProxyInfo): void

    /**
     * Called when screen sharing or capturing is detected.
     * 
     * Platforms: Apple, Android
     * 
     * @param info Information about screen sharing.
     */
    screenSharingDetected(info: ScreenSharingInfo): void

    /**
     * Called when VPN status is changed
     * 
     * Platforms: Apple, Android
     * @param active VPN status
     */
    vpnDetected(active: boolean): void

    // Android specific

    /**
     * Called when application detects a change in enabled screen readers.
     *
     * Platforms: Android
     *
     * @param info Information about detected screen readers.
     */
    screenReaderDetected(info: ScreenReaderInfo): void

    /**
     * Called when tapjacking is detected.
     * 
     * Platforms: Android 
     * @param info Information about detected tapjacking.
     */
    tapjackingDetected(info: TapjackingInfo): void

    /**
     * Called when ADB status is changed. (TODO)
     * 
     * Platforms: Android
     * 
     * @param adbStatus ADB status changed
     */
    adbStatusDetected(adbStatus: boolean): void

    /**
     * Call when active call detection changes are detected.
     * 
     * Platforms: Android
     * 
     * @param info Information about active call.
     */
    activeCallDetected(info: ActiveCallInfo): void

    /**
     * Called when app presence changes.
     * 
     * Platforms: Android
     * 
     * @param info Information about app presence.
     */
    appPresenceChangeDetected(info: AppPresenceInfo): void


    // Apple specific

    /**
     * Called after user took the screenshot.
     * 
     * Platforms: Apple
     */
    userScreenshotDetected(): void

    /**
     * Called when reverse engineering tools detected on the system.
     * 
     * Platforms: Apple
     */
    reverseEngineeringToolsDetected(): void

    /**
     * Called when device passcode configuration is changed.
     * 
     * Platforms: Apple
     * 
     * @param enabled Passcode is enabled or disabled.
     */
    systemPasscodeConfigurationChanged(enabled: boolean): void

    /**
     * Called when device's biometry configuration is changed.
     *
     * Platforms: Apple
     * 
     * @param enabled Biometry is enabled or disabled.
     */
    systemBiometryConfigurationChanged(enabled: boolean): void

    /**
     * Called when the status of phone call is changed.
     *
     * Platforms: Apple
     * 
     * @param isOnCall `true` if there's active call right now.
     */
    isOnCallChanged(isOnCall: boolean): void
}
