// (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 { CoreConstants } from '@/core/constants';
import { CoreError } from '@classes/errors/error';
import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSitesReadingStrategy } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreWS, CoreWSAjaxPreSets, CoreWSExternalWarning } from '@services/ws';
import { CorePath } from '@singletons/path';

/**
 * Class that represents a Moodle site where the user still hasn't authenticated.
 */
export class CoreUnauthenticatedSite {

    siteUrl: string;

    protected publicConfig?: CoreSitePublicConfigResponse;

    /**
     * Create a site.
     *
     * @param siteUrl Site URL.
     * @param publicConfig Site public config.
     */
    constructor(siteUrl: string, publicConfig?: CoreSitePublicConfigResponse) {
        this.siteUrl = CoreUrlUtils.removeUrlParams(siteUrl); // Make sure the URL doesn't have params.
        if (publicConfig) {
            this.setPublicConfig(publicConfig);
        }
    }

    /**
     * Get site URL.
     *
     * @returns Site URL.
     */
    getURL(): string {
        return this.siteUrl;
    }

    /**
     * Set site URL.
     *
     * @param url Site URL.
     */
    setURL(url: string): void {
        this.siteUrl = url;
    }

    /**
     * Get site info.
     *
     * @returns Site info.
     */
    getInfo(): CoreSiteInfo | undefined {
        // Cannot retrieve info for unauthenticated sites.
        return undefined;
    }

    /**
     * Check if the site has info with the given key and it doesn't contain an empty value.
     *
     * @param key Info key.
     * @returns Whether the key is filled within site info.
     */
    hasInfo(key: string): boolean {
        const info = this.getInfo()?.[key] ?? null;

        return info === false || info === 0 || !!info;
    }

    /**
     * Get site name.
     *
     * @returns Site name.
     */
    async getSiteName(): Promise<string> {
        if (this.isDemoModeSite()) {
            return CoreConstants.CONFIG.appname;
        }

        const siteName = this.getInfo()?.sitename || this.publicConfig?.sitename;
        if (siteName) {
            return siteName;
        }

        // Fallback.
        const isSingleFixedSite = await CoreLoginHelper.isSingleFixedSite();

        if (isSingleFixedSite) {
            const sites = await CoreLoginHelper.getAvailableSites();

            return sites[0].name;
        }

        return '';
    }

    /**
     * Check whether the app should use the local logo instead of the remote one.
     *
     * @returns Whether local logo is forced.
     */
    forcesLocalLogo(): boolean {
        return CoreConstants.CONFIG.forceLoginLogo || this.isDemoModeSite();
    }

    /**
     * Get logo URL from a site public config.
     *
     * @param config Site public config.
     * @returns Logo URL.
     */
    getLogoUrl(config?: CoreSitePublicConfigResponse): string | undefined {
        config = config ?? this.publicConfig;
        if (!config || this.forcesLocalLogo()) {
            return 'assets/img/login_logo.png';
        }

        return config.logourl || config.compactlogourl || 'assets/img/login_logo.png';
    }

    /**
     * Returns a url to link an specific page on the site.
     *
     * @param path Path of the url to go to.
     * @param params Object with the params to add.
     * @param anchor Anchor text if needed.
     * @returns URL with params.
     */
    createSiteUrl(path: string, params?: Record<string, unknown>, anchor?: string): string {
        return CoreUrlUtils.addParamsToUrl(CorePath.concatenatePaths(this.siteUrl, path), params, anchor);
    }

    /**
     * Check if a URL belongs to this site.
     *
     * @param url URL to check.
     * @returns Whether the URL belongs to this site.
     */
    containsUrl(url?: string): boolean {
        if (!url) {
            return false;
        }

        const siteUrl = CoreTextUtils.addEndingSlash(CoreUrlUtils.removeProtocolAndWWW(this.siteUrl));
        url = CoreTextUtils.addEndingSlash(CoreUrlUtils.removeProtocolAndWWW(url));

        return url.indexOf(siteUrl) == 0;
    }

    /**
     * Get the public config of this site.
     *
     * @param options Options.
     * @returns Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax.
     */
    async getPublicConfig(options: { readingStrategy?: CoreSitesReadingStrategy } = {}): Promise<CoreSitePublicConfigResponse> {
        const ignoreCache = options.readingStrategy === CoreSitesReadingStrategy.ONLY_NETWORK ||
            options.readingStrategy ===  CoreSitesReadingStrategy.PREFER_NETWORK;
        if (!ignoreCache && this.publicConfig) {
            return this.publicConfig;
        }

        if (options.readingStrategy === CoreSitesReadingStrategy.ONLY_CACHE) {
            throw new CoreError('Cache not available to read public config');
        }

        try {
            const config = await this.requestPublicConfig();

            this.setPublicConfig(config);

            return config;
        } catch (error) {
            if (options.readingStrategy === CoreSitesReadingStrategy.ONLY_NETWORK || !this.publicConfig) {
                throw error;
            }

            return this.publicConfig;
        }
    }

    /**
     * Set public config.
     *
     * @param publicConfig Public config.
     */
    setPublicConfig(publicConfig: CoreSitePublicConfigResponse): void {
        publicConfig.tool_mobile_disabledfeatures =
            CoreTextUtils.treatDisabledFeatures(publicConfig.tool_mobile_disabledfeatures ?? '');
        this.publicConfig = publicConfig;
    }

    /**
     * Perform a request to the server to get the public config of this site.
     *
     * @returns Promise resolved with public config.
     */
    protected async requestPublicConfig(): Promise<CoreSitePublicConfigResponse> {
        const preSets: CoreWSAjaxPreSets = {
            siteUrl: this.siteUrl,
        };

        let config: CoreSitePublicConfigResponse;

        try {
            config = await CoreWS.callAjax<CoreSitePublicConfigResponse>('tool_mobile_get_public_config', {}, preSets);
        } catch (error) {
            if (!error || error.errorcode !== 'codingerror' || (this.getInfo() && !this.isAjaxGetSupported())) {
                throw error;
            }

            // This error probably means that there is a redirect in the site. Try to use a GET request.
            preSets.noLogin = true;
            preSets.useGet = true;

            try {
                config = await CoreWS.callAjax<CoreSitePublicConfigResponse>('tool_mobile_get_public_config', {}, preSets);
            } catch (error2) {
                if (this.isAjaxGetSupported()) {
                    // GET is supported, return the second error.
                    throw error2;
                } else {
                    // GET not supported or we don't know if it's supported. Return first error.
                    throw error;
                }
            }
        }

        // Use the wwwroot returned by the server.
        if (config.httpswwwroot) {
            this.siteUrl = CoreUrlUtils.removeUrlParams(config.httpswwwroot); // Make sure the URL doesn't have params.
        }

        return config;
    }

    /**
     * Check if GET method is supported for AJAX calls.
     *
     * @returns Whether it's supported.
     * @since Moodle 3.8
     */
    protected isAjaxGetSupported(): boolean {
        // We don't know if it's supported, assume it's not.
        return false;
    }

    /**
     * Check if a URL to a file belongs to the site and uses the pluginfileurl or tokenpluginfileurl endpoints.
     *
     * @param url File URL to check.
     * @returns Whether it's a site file URL.
     */
    isSitePluginFileUrl(url: string): boolean {
        const isPluginFileUrl = CoreUrlUtils.isPluginFileUrl(url) || CoreUrlUtils.isTokenPluginFileUrl(url);
        if (!isPluginFileUrl) {
            return false;
        }

        return this.containsUrl(url);
    }

    /**
     * Check if a URL to a file belongs to the site and is a theme image file.
     *
     * @param url File URL to check.
     * @returns Whether it's a site theme image URL.
     */
    isSiteThemeImageUrl(url: string): boolean {
        if (!CoreUrlUtils.isThemeImageUrl(url)) {
            return false;
        }

        return this.containsUrl(url);
    }

    /**
     * Check if the site is a demo mode site.
     *
     * @returns Whether the site is a demo mode site.
     */
    isDemoModeSite(): boolean {
        const demoSiteData = CoreLoginHelper.getDemoModeSiteInfo();

        return this.containsUrl(demoSiteData?.url);
    }

    /**
     * Check whether informative links should be displayed for this site.
     *
     * @returns Whether informative links should be displayed.
     */
    shouldDisplayInformativeLinks(): boolean {
        return !CoreConstants.CONFIG.hideInformativeLinks && !this.isDemoModeSite();
    }

    /**
     * Check if a certain feature is disabled in the site.
     *
     * @param name Name of the feature to check.
     * @returns Whether it's disabled.
     */
    isFeatureDisabled(name: string): boolean {
        const disabledFeatures = this.getDisabledFeatures();
        if (!disabledFeatures) {
            return false;
        }

        const regEx = new RegExp('(,|^)' + CoreTextUtils.escapeForRegex(name) + '(,|$)', 'g');

        return !!disabledFeatures.match(regEx);
    }

    /**
     * Get disabled features string.
     *
     * @returns Disabled features.
     */
    protected getDisabledFeatures(): string | undefined {
        return this.publicConfig?.tool_mobile_disabledfeatures;
    }

}

/**
 * Result of WS core_webservice_get_site_info.
 */
export type CoreSiteInfoResponse = {
    sitename: string; // Site name.
    username: string; // Username.
    firstname: string; // First name.
    lastname: string; // Last name.
    fullname: string; // User full name.
    lang: string; // Current language.
    userid: number; // User id.
    siteurl: string; // Site url.
    userpictureurl: string; // The user profile picture.
    functions: {
        name: string; // Function name.
        version: string; // The version number of the component to which the function belongs.
    }[];
    downloadfiles?: number; // 1 if users are allowed to download files, 0 if not.
    uploadfiles?: number; // 1 if users are allowed to upload files, 0 if not.
    release?: string; // Moodle release number.
    version?: string; // Moodle version number.
    mobilecssurl?: string; // Mobile custom CSS theme.
    advancedfeatures?: { // Advanced features availability.
        name: string; // Feature name.
        value: number; // Feature value. Usually 1 means enabled.
    }[];
    usercanmanageownfiles?: boolean; // True if the user can manage his own files.
    userquota?: number; // User quota (bytes). 0 means user can ignore the quota.
    usermaxuploadfilesize?: number; // User max upload file size (bytes). -1 means the user can ignore the upload file size.
    userhomepage?: CoreSiteInfoUserHomepage; // The default home page for the user.
    userprivateaccesskey?: string; // Private user access key for fetching files.
    siteid?: number; // Site course ID.
    sitecalendartype?: string; // Calendar type set in the site.
    usercalendartype?: string; // Calendar typed used by the user.
    userissiteadmin?: boolean; // Whether the user is a site admin or not.
    theme?: string; // Current theme for the user.
    limitconcurrentlogins?: number; // @since 4.0. Number of concurrent sessions allowed.
    usersessionscount?: number; // @since 4.0. Number of active sessions for current user. Only if limitconcurrentlogins is used.
    policyagreed?: number; // @since 4.4. Whether user accepted all the policies.
};

/**
 * Site info, including some calculated data.
 */
export type CoreSiteInfo = CoreSiteInfoResponse & {
    functionsByName?: {
        [name: string]: {
            name: string; // Function name.
            version: string; // The version number of the component to which the function belongs.
        };
    };
};

/**
 * Enum constants that define default user home page.
 */
export enum CoreSiteInfoUserHomepage {
    HOMEPAGE_SITE = 0, // Site home.
    HOMEPAGE_MY = 1, // Dashboard.
    HOMEPAGE_MYCOURSES = 3, // My courses.
}

/**
 * Possible values for 'supportavailability' config.
 */
export const enum CoreSiteConfigSupportAvailability {
    Disabled = 0,
    Authenticated = 1,
    Anyone = 2,
}

/**
 * Result of WS tool_mobile_get_public_config.
 */
export type CoreSitePublicConfigResponse = {
    wwwroot: string; // Site URL.
    httpswwwroot: string; // Site https URL (if httpslogin is enabled).
    sitename: string; // Site name.
    guestlogin: number; // Whether guest login is enabled.
    rememberusername: number; // Values: 0 for No, 1 for Yes, 2 for optional.
    authloginviaemail: number; // Whether log in via email is enabled.
    registerauth: string; // Authentication method for user registration.
    forgottenpasswordurl: string; // Forgotten password URL.
    authinstructions: string; // Authentication instructions.
    authnoneenabled: number; // Whether auth none is enabled.
    enablewebservices: number; // Whether Web Services are enabled.
    enablemobilewebservice: number; // Whether the Mobile service is enabled.
    maintenanceenabled: number; // Whether site maintenance is enabled.
    maintenancemessage: string; // Maintenance message.
    logourl?: string; // The site logo URL.
    compactlogourl?: string; // The site compact logo URL.
    typeoflogin: TypeOfLogin; // The type of login. 1 for app, 2 for browser, 3 for embedded.
    launchurl?: string; // SSO login launch URL.
    mobilecssurl?: string; // Mobile custom CSS theme.
    // eslint-disable-next-line @typescript-eslint/naming-convention
    tool_mobile_disabledfeatures?: string; // Disabled features in the app.
    identityproviders?: CoreSiteIdentityProvider[]; // Identity providers.
    country?: string; // Default site country.
    agedigitalconsentverification?: boolean; // Whether age digital consent verification is enabled.
    supportname?: string; // Site support contact name (only if age verification is enabled).
    supportemail?: string; // Site support contact email (only if age verification is enabled).
    supportavailability?: CoreSiteConfigSupportAvailability;
    supportpage?: string; // Site support contact url.
    autolang?: number; // Whether to detect default language from browser setting.
    lang?: string; // Default language for the site.
    langmenu?: number; // Whether the language menu should be displayed.
    langlist?: string; // Languages on language menu.
    locale?: string; // Sitewide locale.
    // eslint-disable-next-line @typescript-eslint/naming-convention
    tool_mobile_minimumversion?: string; // Minimum required version to access.
    // eslint-disable-next-line @typescript-eslint/naming-convention
    tool_mobile_iosappid?: string; // IOS app's unique identifier.
    // eslint-disable-next-line @typescript-eslint/naming-convention
    tool_mobile_androidappid?: string; // Android app's unique identifier.
    // eslint-disable-next-line @typescript-eslint/naming-convention
    tool_mobile_setuplink?: string; // App download page.
    tool_mobile_qrcodetype?: CoreSiteQRCodeType; // eslint-disable-line @typescript-eslint/naming-convention
    warnings?: CoreWSExternalWarning[];
};

/**
 * QR Code type enumeration.
 */
export enum CoreSiteQRCodeType {
    QR_CODE_DISABLED = 0, // QR code disabled value
    QR_CODE_URL = 1, // QR code type URL value
    QR_CODE_LOGIN = 2, // QR code type login value
}

/**
 * Identity provider.
 */
export type CoreSiteIdentityProvider = {
    name: string; // The identity provider name.
    iconurl: string; // The icon URL for the provider.
    url: string; // The URL of the provider.
};

/**
 * The type of login. 1 for app, 2 for browser, 3 for embedded.
 */
export enum TypeOfLogin {
    APP = 1,
    BROWSER = 2, // SSO in browser window is required.
    EMBEDDED = 3, // SSO in embedded browser is required.
}
