// (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 { FileEntry } from '@ionic-native/file/ngx';

import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { CoreFileUploaderHandlerResult } from '@features/fileuploader/services/fileuploader-delegate';
import { CoreApp } from '@services/app';
import { CoreFile } from '@services/file';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { AlertController, ApplicationInit, makeSingleton, ModalController, Platform, Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { CoreLogger } from '@singletons/logger';
import { CoreSharedFilesListModalComponent } from '../components/list-modal/list-modal';
import { CoreSharedFiles } from './sharedfiles';
import { SHAREDFILES_PAGE_NAME } from '../sharedfiles.module';
import { CoreSharedFilesChooseSitePage } from '../pages/choose-site/choose-site';
import { CoreError } from '@classes/errors/error';

/**
 * Helper service to share files with the app.
 */
@Injectable({ providedIn: 'root' })
export class CoreSharedFilesHelperProvider {

    protected logger: CoreLogger;

    constructor() {
        this.logger = CoreLogger.getInstance('CoreSharedFilesHelperProvider');
    }

    /**
     * Initialize.
     */
    initialize(): void {
        if (!CoreApp.isIOS()) {
            return;
        }

        let lastCheck = Date.now();

        // Check if there are new files at app start and when the app is resumed.
        this.searchIOSNewSharedFiles();

        Platform.resume.subscribe(() => {
            // Wait a bit to make sure that APP_LAUNCHED_URL is treated before this callback.
            setTimeout(() => {
                if (Date.now() - lastCheck < 1000) {
                    // Last check less than 1s ago, don't do anything.
                    return;
                }

                lastCheck = Date.now();
                this.searchIOSNewSharedFiles();
            }, 200);
        });

        CoreEvents.on(CoreEvents.APP_LAUNCHED_URL, (data) => {
            if (data.url.indexOf('file://') === 0) {
                // We received a file in iOS, it's probably a shared file. Treat it.
                lastCheck = Date.now();
                this.searchIOSNewSharedFiles(data.url);
            }
        });
    }

    /**
     * Ask a user if he wants to replace a file (using originalName) or rename it (using newName).
     *
     * @param originalName Original name.
     * @param newName New name.
     * @return Promise resolved with the name to use when the user chooses. Rejected if user cancels.
     */
    async askRenameReplace(originalName: string, newName: string): Promise<string> {
        const alert = await AlertController.create({
            header: Translate.instant('core.sharedfiles.sharedfiles'),
            message: Translate.instant('core.sharedfiles.chooseactionrepeatedfile', { $a: newName }),
            buttons: [
                {
                    text: Translate.instant('core.sharedfiles.rename'),
                    role: 'rename',
                },
                {
                    text: Translate.instant('core.sharedfiles.replace'),
                    role: 'replace',
                },
            ],
        });

        await alert.present();

        const result = await alert.onDidDismiss();

        if (result.role == 'rename') {
            return newName;
        } else if (result.role == 'replace') {
            return originalName;
        } else {
            // Canceled.
            throw new CoreCanceledError();
        }
    }

    /**
     * Go to the choose site view.
     *
     * @param filePath File path to send to the view.
     * @param isInbox Whether the file is in the Inbox folder.
     */
    goToChooseSite(filePath: string, isInbox?: boolean): void {
        if (CoreSites.isLoggedIn()) {
            CoreNavigator.navigateToSitePath(`/${SHAREDFILES_PAGE_NAME}/choosesite`, {
                params: { filePath, isInbox },
            });
        } else {
            CoreNavigator.navigate(`/${SHAREDFILES_PAGE_NAME}/choosesite`, {
                params: { filePath, isInbox },
            });
        }
    }

    /**
     * Whether the user is already choosing a site to store a shared file.
     *
     * @return Whether the user is already choosing a site to store a shared file.
     */
    protected isChoosingSite(): boolean {
        return CoreNavigator.getCurrentRoute({ pageComponent: CoreSharedFilesChooseSitePage }) !== null;
    }

    /**
     * Open the view to select a shared file.
     *
     * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported.
     * @return Promise resolved when a file is picked, rejected if file picker is closed without selecting a file.
     */
    async pickSharedFile(mimetypes?: string[]): Promise<CoreFileUploaderHandlerResult> {
        const modal = await ModalController.create({
            component: CoreSharedFilesListModalComponent,
            cssClass: 'core-modal-fullscreen',
            componentProps: { mimetypes, pick: true },
        });

        await modal.present();

        const result = await modal.onDidDismiss();
        const file: FileEntry | undefined = result.data;

        if (!file) {
            // User cancelled.
            throw new CoreCanceledError();
        }

        const error = CoreFileUploader.isInvalidMimetype(mimetypes, file.fullPath);
        if (error) {
            throw new CoreError(error);
        }

        return {
            path: file.fullPath,
            treated: false,
        };
    }

    /**
     * Delete a shared file.
     *
     * @param fileEntry The file entry to delete.
     * @param isInbox Whether the file is in the Inbox folder.
     * @return Promise resolved when done.
     */
    protected removeSharedFile(fileEntry: FileEntry, isInbox?: boolean): Promise<void> {
        if (isInbox) {
            return CoreSharedFiles.deleteInboxFile(fileEntry);
        } else {
            return CoreFile.removeFileByFileEntry(fileEntry);
        }
    }

    /**
     * Checks if there is a new file received in iOS and move it to the shared folder of current site.
     * If more than one site is found, the user will have to choose the site where to store it in.
     * If more than one file is found, treat only the first one.
     *
     * @param path Path to a file received when launching the app.
     * @return Promise resolved when done.
     */
    async searchIOSNewSharedFiles(path?: string): Promise<void> {
        try {
            await ApplicationInit.donePromise;

            if (this.isChoosingSite()) {
                // We're already treating a shared file. Abort.
                return;
            }

            let fileEntry: FileEntry | undefined;
            if (path) {
                // The app was launched with the path to the file, get the file.
                fileEntry = await CoreFile.getExternalFile(path);
            } else {
                // No path received, search if there is any file in the Inbox folder.
                fileEntry = await CoreSharedFiles.checkIOSNewFiles();
            }

            if (!fileEntry) {
                return;
            }

            const siteIds = await CoreSites.getSitesIds();

            if (!siteIds.length) {
                // No sites stored, show error and delete the file.
                CoreDomUtils.showErrorModal('core.sharedfiles.errorreceivefilenosites', true);

                return this.removeSharedFile(fileEntry, !path);
            } else if (siteIds.length == 1) {
                return this.storeSharedFileInSite(fileEntry, siteIds[0], !path);
            } else if (!this.isChoosingSite()) {
                this.goToChooseSite(fileEntry.toURL(), !path);
            }
        } catch (error) {
            if (error) {
                this.logger.error('Error searching iOS new shared files', error, path);
            }
        }
    }

    /**
     * Store a shared file in a site's shared files folder.
     *
     * @param fileEntry Shared file entry.
     * @param siteId Site ID. If not defined, current site.
     * @param isInbox Whether the file is in the Inbox folder.
     * @return Promise resolved when done.
     */
    async storeSharedFileInSite(fileEntry: FileEntry, siteId?: string, isInbox?: boolean): Promise<void> {
        siteId = siteId || CoreSites.getCurrentSiteId();

        // First of all check if there's already a file with the same name in the shared files folder.
        const sharedFilesDirPath = CoreSharedFiles.getSiteSharedFilesDirPath(siteId);

        let newName = await CoreFile.getUniqueNameInFolder(sharedFilesDirPath, fileEntry.name);

        if (newName.toLowerCase() != fileEntry.name.toLowerCase()) {
            // Repeated name. Ask the user what he wants to do.
            newName = await this.askRenameReplace(fileEntry.name, newName);
        }

        try {
            await CoreSharedFiles.storeFileInSite(fileEntry, newName, siteId);
        } catch (error) {
            CoreDomUtils.showErrorModal(error || 'Error moving file.');
        } finally {
            this.removeSharedFile(fileEntry, isInbox);
            CoreDomUtils.showAlertTranslated('core.success', 'core.sharedfiles.successstorefile');
        }
    }

}

export const CoreSharedFilesHelper = makeSingleton(CoreSharedFilesHelperProvider);
