// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Injectable } from '@angular/core';

import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreWSExternalWarning } from '@services/ws';
import { CoreConstants } from '@/core/constants';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreError } from '@classes/errors/error';
import { CoreWSError } from '@classes/errors/wserror';
import { makeSingleton, Translate } from '@singletons';
import { CoreEvents, CoreEventSiteData } from '@singletons/events';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { CorePath } from '@singletons/path';

const ROOT_CACHE_KEY = 'mmaMessageOutputAirnotifier:';

/**
 * Service to handle Airnotifier message output.
 */
@Injectable({ providedIn: 'root' })
export class AddonMessageOutputAirnotifierProvider {

    /**
     * Initialize.
     */
    initialize(): void {
        CoreEvents.on(CoreEvents.DEVICE_REGISTERED_IN_MOODLE, async (data: CoreEventSiteData) => {
            // Get user devices to make Moodle send the devices data to Airnotifier.
            this.getUserDevices(true, data.siteId);
        });

        CoreEvents.on(CoreEvents.LOGIN, (data) => {
            this.warnPushDisabledForAdmin(data.siteId);
        });
    }

    /**
     * Enables or disables a device.
     *
     * @param deviceId Device ID.
     * @param enable True to enable, false to disable.
     * @param siteId Site ID. If not defined, current site.
     * @returns Promise resolved if success.
     */
    async enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise<void> {
        const site = await CoreSites.getSite(siteId);

        const data: AddonMessageOutputAirnotifierEnableDeviceWSParams = {
            deviceid: deviceId,
            enable: !!enable,
        };

        const result = await site.write<AddonMessageOutputAirnotifierEnableDeviceWSResponse>(
            'message_airnotifier_enable_device',
            data,
        );

        if (result.success) {
            return;
        }

        // Fail. Reject with warning message if any.
        if (result.warnings?.length) {
            throw new CoreWSError(result.warnings[0]);
        }

        throw new CoreError('Error enabling device');
    }

    /**
     * Get the cache key for the is system configured call.
     *
     * @returns Cache key.
     */
    protected getSystemConfiguredCacheKey(): string {
        return ROOT_CACHE_KEY + 'isAirnotifierConfigured';
    }

    /**
     * Check if airnotifier is configured.
     *
     * @param options Options.
     * @returns Promise resolved with boolean: whether it's configured.
     */
    async isSystemConfigured(options: CoreSitesCommonWSOptions = {}): Promise<boolean> {
        const site = await CoreSites.getSite(options.siteId);

        const preSets: CoreSiteWSPreSets = {
            cacheKey: this.getSystemConfiguredCacheKey(),
            updateFrequency: CoreSite.FREQUENCY_RARELY,
            ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
        };

        const result = await site.read<number>('message_airnotifier_is_system_configured', {}, preSets);

        return result === 1;
    }

    /**
     * Get the cache key for the get user devices call.
     *
     * @returns Cache key.
     */
    protected getUserDevicesCacheKey(): string {
        return ROOT_CACHE_KEY + 'userDevices';
    }

    /**
     * Get user devices.
     *
     * @param ignoreCache Whether to ignore cache.
     * @param siteId Site ID. If not defined, use current site.
     * @returns Promise resolved with the devices.
     */
    async getUserDevices(ignoreCache?: boolean, siteId?: string): Promise<AddonMessageOutputAirnotifierDevice[]> {

        const site = await CoreSites.getSite(siteId);

        const data: AddonMessageOutputAirnotifierGetUserDevicesWSParams = {
            appid: CoreConstants.CONFIG.app_id,
        };
        const preSets: CoreSiteWSPreSets = {
            cacheKey: this.getUserDevicesCacheKey(),
            updateFrequency: CoreSite.FREQUENCY_RARELY,
        };

        if (ignoreCache) {
            preSets.getFromCache = false;
            preSets.emergencyCache = false;
        }

        const result = await site.read<AddonMessageOutputAirnotifierGetUserDevicesWSResponse>(
            'message_airnotifier_get_user_devices',
            data,
            preSets,
        );

        return result.devices;
    }

    /**
     * Invalidate get user devices.
     *
     * @param siteId Site ID. If not defined, current site.
     * @returns Promise resolved when data is invalidated.
     */
    async invalidateUserDevices(siteId?: string): Promise<void> {
        const site = await CoreSites.getSite(siteId);

        return site.invalidateWsCacheForKey(this.getUserDevicesCacheKey());
    }

    /**
     * Is user is an admin and push are disabled, notify him.
     *
     * @param siteId Site ID.
     * @returns Promise resolved when done.
     */
    protected async warnPushDisabledForAdmin(siteId?: string): Promise<void> {
        if (!siteId) {
            return;
        }

        try {
            const site = await CoreSites.getSite(siteId);

            if (!site.getInfo()?.userissiteadmin) {
                // Not an admin or we don't know, stop.
                return;
            }

            // Check if the admin already asked not to be reminded.
            const dontAsk = await site.getLocalSiteConfig('AddonMessageOutputAirnotifierDontRemindDisabled', 0);
            if (dontAsk) {
                return;
            }

            // Check if airnotifier is configured.
            const isConfigured = await this.isSystemConfigured({
                readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
                siteId,
            });

            if (isConfigured) {
                return;
            }

            // Warn the admin.
            const dontShowAgain = await CoreDomUtils.showPrompt(
                Translate.instant('addon.messageoutput_airnotifier.pushdisabledwarning'),
                undefined,
                Translate.instant('core.dontshowagain'),
                'checkbox',
                [
                    {
                        text: Translate.instant('core.ok'),
                    },
                    {
                        text: Translate.instant('core.goto', { $a: Translate.instant('core.settings.settings') }),
                        handler: (data, resolve) => {
                            resolve(data[0]);

                            const url = CorePath.concatenatePaths(
                                site.getURL(),
                                site.isVersionGreaterEqualThan('3.11') ?
                                    'message/output/airnotifier/checkconfiguration.php' :
                                    'admin/message.php',
                            );

                            // Don't try auto-login, admins cannot use it.
                            CoreUtils.openInBrowser(url, {
                                showBrowserWarning: false,
                            });
                        },
                    },
                ],
            );

            if (dontShowAgain) {
                await site.setLocalSiteConfig('AddonMessageOutputAirnotifierDontRemindDisabled', 1);
            }
        } catch {
            // Ignore errors.
        }
    }

}

export const AddonMessageOutputAirnotifier = makeSingleton(AddonMessageOutputAirnotifierProvider);

/**
 * Device data returned by WS message_airnotifier_get_user_devices.
 */
export type AddonMessageOutputAirnotifierDevice = {
    id: number; // Device id (in the message_airnotifier table).
    appid: string; // The app id, something like com.moodle.moodlemobile.
    name: string; // The device name, 'occam' or 'iPhone' etc.
    model: string; // The device model 'Nexus4' or 'iPad1,1' etc.
    platform: string; // The device platform 'iOS' or 'Android' etc.
    version: string; // The device version '6.1.2' or '4.2.2' etc.
    pushid: string; // The device PUSH token/key/identifier/registration id.
    uuid: string; // The device UUID.
    enable: number | boolean; // Whether the device is enabled or not.
    timecreated: number; // Time created.
    timemodified: number; // Time modified.
};

/**
 * Params of message_airnotifier_enable_device WS.
 */
export type AddonMessageOutputAirnotifierEnableDeviceWSParams = {
    deviceid: number; // The device id.
    enable: boolean; // True for enable the device, false otherwise.
};

/**
 * Result of WS message_airnotifier_enable_device.
 */
export type AddonMessageOutputAirnotifierEnableDeviceWSResponse = {
    success: boolean; // True if success.
    warnings?: CoreWSExternalWarning[];
};

/**
 * Params of message_airnotifier_get_user_devices WS.
 */
export type AddonMessageOutputAirnotifierGetUserDevicesWSParams = {
    appid: string; // App unique id (usually a reversed domain).
    userid?: number; // User id, 0 for current user.
};

/**
 * Result of WS message_airnotifier_get_user_devices.
 */
export type AddonMessageOutputAirnotifierGetUserDevicesWSResponse = {
    devices: AddonMessageOutputAirnotifierDevice[]; // List of devices.
    warnings?: CoreWSExternalWarning[];
};
