//
// 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 { Platform } from "react-native"
import { EventHelper } from "./internal/EventHelper"
import { MalwarelyticsModuleIfc, wrapNativeCall } from "./internal/MalwarelyticsModule"
import { MalwarelyticsError } from "./MalwarelyticsError"
import { SmartProtectionResult } from "./model/antivirus/SmartProtectionResult"
import { ApkThreat, ThreatIndex, threatIndexToNumber } from "./model/antivirus/ApkThreat"
import { ApkInfo } from "./model/antivirus/Apk"
import { ApkThreatEvent } from "./internal/ApkThreatEvent"
import { ObservedUpdateInfo } from "./model/antivirus/update/ObservedUpdateInfo"
import { UpdateInfo } from "./model/antivirus/update/UpdateInfo"

/**
 * The `MalwarelyticsAntivirus` class provides antivirus functionality from Malwarelytics for Android SDK.
 * Be aware that you must evaluate whether the funcionality is supported on the platform, by getting
 * `isSupported` property. All other methods from the class fails with error if antivirus is not supported
 * on the platform.
 */
export class MalwarelyticsAntivirus {

    constructor(eventHelper: EventHelper) {
        if (Platform.OS == 'android') {
            this.module = eventHelper.module            
        }
        this.eventHelper = eventHelper
        this.isSupported = this.module != undefined
    }

    /**
     * Determine whether the Antivirus module is supported on the platform.
     */
    readonly isSupported: boolean

    /**
     * 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 update events.
     */
    private updateEventSubscription: EmitterSubscription | undefined

    /**
     * Object representing a subscription to apk threat events.
     */
    private apkThreatEventSubscription: EmitterSubscription | undefined

    /**
     * Test whether antivirus is enabled.
     */
    isEnabled(): Promise<boolean> {
        return this.withModule(module => module.isAntivirusEnabled())
    }

    /**
     * Set custom localization to be used when a UI is shown.
     * This overrides system settings. Use ISO 639-1 language code or `undefined`. 
     * Undefined value will reset the custom setting and the system settings will be used again
     */
    setCustomLocalization(languageCode: string | undefined): Promise<void> {
        return this.withModule(module => module.setAntivirusLocalization(languageCode))
    }

    /**
     * Get custom localization currently in use by antivirus.
     */
    getCustomLocalization(): Promise<string | undefined> {
        return this.withModule(module => module.getAntivirusLocalization())
    }

    /**
     * Schedules a job that will execute smart protection run. Note that this is asynchronous and
     * can take some time.
     * 
     * Note that you can call this function with multiple times, but you'll get the result once the internal
     * evaluation is finished.
     * 
     * @param performOnlineUpdate Optional argument to indicate that online update is not desired. If false only local data will be used.
     */
    triggerSmartProtection(performOnlineUpdate: boolean = true): Promise<SmartProtectionResult> {
        return this.withModule(module => module.triggerSmartProtection(performOnlineUpdate))
    }

    /**
     * Get information whether smart protection evaluation is already running.
     */
    isSmartProtectionTriggered(): Promise<boolean> {
        return this.withModule(module => module.isSmartProtectionTriggered())
    }

    /**
     * Returns list of all applications with the malware evaluation.
     */
    getThreatList(): Promise<ApkThreat[]> {
        return this.withModule(module => module.getThreatList())
    }

    /**
     * Returns list of all applications with the malware evaluation higher or equal than specified.
     * @param minThreatIndex Minimal threat index.
     * @returns List of all applications with the malware evaluation higher or equal than specified.
     */
    async getFilteredThreatList(minThreatIndex: ThreatIndex): Promise<ApkThreat[]> {
        const minIndex = threatIndexToNumber(minThreatIndex)
        return (await this.getThreatList()).filter(item => threatIndexToNumber(item.threatIndex) >= minIndex)
    }

    /**
     * Gets more information for the package name of the application.
     * 
     * @param packageName Package name of the application
     * @returns Information about package or `undefined` when no such information is available.
     */
    getApkInfo(packageName: string): Promise<ApkInfo | undefined> {
        return this.withModule(module => module.getApkInfo(packageName))
    }

    /**
     * Get information about the last updates.
     *
     * Primarily intended for troubleshooting.
     *
     * @returns Information about latest update successes and failures.
     */
    getLastUpdateInfo(): Promise<UpdateInfo> {
        return this.withModule(module => module.getLastUpdateInfo());
    }

    /** Set listener that is triggered when a suggestion update completes. */
    async setUpdateListener(listener: MalwarelyticsAndroidUpdateListener): Promise<void> {
        this.updateEventSubscription?.remove()
        if (this.module == undefined) {
            return Promise.reject(new MalwarelyticsError('METHOD_NOT_SUPPORTED', `Method is not supported on ${Platform.OS} platform`))
        }
        this.updateEventSubscription = await this.eventHelper.addListener('Malwarelytics.AV_UPDATE', (data) => {
            // console.log(`${Platform.OS}: AV_UPDATE event: ${JSON.stringify(data)}`)
            const oui = data as ObservedUpdateInfo
            listener.onSuggestionUpdated(oui)
        })
    }

    /** Remove listener for suggestion updates. */
    removeUpdateListener() {
        this.updateEventSubscription?.remove()
        this.updateEventSubscription = undefined
    }

    /** Set listener that is triggered whan an app is installed/updated/uninstall. */
    async setApkThreatListener(listener: MalwarelyticsAndroidApkThreatListener): Promise<void> {
        this.apkThreatEventSubscription?.remove()
        if (this.module == undefined) {
            return Promise.reject(new MalwarelyticsError('METHOD_NOT_SUPPORTED', `Method is not supported on ${Platform.OS} platform`))
        }
        this.apkThreatEventSubscription = await this.eventHelper.addListener('Malwarelytics.AV_APK_THREAT', (data) => {
            // console.log(`${Platform.OS}: AV_APK_THREAT event: ${JSON.stringify(data)}`)
            const event = data as ApkThreatEvent
            switch (event.type) {
                case "INSTALL":
                    listener.onInstallDetected(event.payload as ApkThreat)
                    break
                case "UPDATE":
                    listener.onUpdateDetected(event.payload as ApkThreat)
                    break
                case "UNINSTALL":
                    listener.onUninstallDetected(event.payload as string)
                    break
                default:
                    console.warn(`${Platform.OS}: Unsupported APK THREAT event ${event.type}`)
                    break
            }
        })
    }

    /** Remove listgener for app installs/updates/uninstall. */
    removeApkThreatListener() {
        this.apkThreatEventSubscription?.remove()
        this.apkThreatEventSubscription = undefined
    }

    /**
     * Execute closure with action with properly acquired instnace to native module.
     * @param action Action to execute.
     * @returns Result returned from the action.
     */
    private withModule<T>(action: (nativeModule: MalwarelyticsModuleIfc) => Promise<T>): Promise<T> {
        if (this.module != undefined) {
            return wrapNativeCall(this.module, action)
        }
        return Promise.reject(new MalwarelyticsError('METHOD_NOT_SUPPORTED', `Method is not supported on ${Platform.OS} platform`))
    }
}

/** Listener for update results. */
export interface MalwarelyticsAndroidUpdateListener {
    /** 
     * Called when an update was finished regardless of a success or a failure.
     *
     * Returned data indicated the update result:
     * - Successful update:
     *     @see ObservedUpdateInfo.failureReason is null
     * - Partially successful update:
     *     @see ObservedUpdateInfo.failureReason is not null
     *     and @see ObservedUpdateInfo.updatedApps is not empty
     * - Failed update:
     *     and @see ObservedUpdateInfo.failureReason is not null
     *     and @see ObservedUpdateInfo.updatedApps is empty */
    onSuggestionUpdated(observedUpdateInfo: ObservedUpdateInfo): void;
}

/** Listener for updates to the apps on the device. */
export interface MalwarelyticsAndroidApkThreatListener {
    /**
     * Called whan an app install is detected.
     * @param apkThread The new threat.
     */
    onInstallDetected(apkThreat: ApkThreat): void;
    /**
     * Called when an app update is detected.
     * @param apkThread The changed threat.
     */
    onUpdateDetected(apkThreat: ApkThreat): void;
    /**
     * Called when an app uninstall is detected.
     * @param packageName The package name of the deleted threat.
     */
    onUninstallDetected(packageName: string): void;
}