// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {DeviceEventEmitter} from 'react-native';

import {Events, Calls} from '@constants';
import {t} from '@i18n';
import {setServerCredentials} from '@init/credentials';
import {Analytics, create} from '@managers/analytics';
import {semverFromServerVersion} from '@utils/server';

import * as ClientConstants from './constants';
import ClientError from './error';

import type {
    APIClientInterface,
    ClientHeaders,
    ClientResponse,
    RequestOptions,
} from '@mattermost/react-native-network-client';

export default class ClientBase {
    analytics: Analytics|undefined;
    apiClient: APIClientInterface;
    csrfToken = '';
    requestHeaders: {[x: string]: string} = {};
    serverVersion = '';
    urlVersion = '/api/v4';

    constructor(apiClient: APIClientInterface, serverUrl: string, bearerToken?: string, csrfToken?: string) {
        this.apiClient = apiClient;
        this.analytics = create(serverUrl);

        if (bearerToken) {
            this.setBearerToken(bearerToken);
        }
        if (csrfToken) {
            this.setCSRFToken(csrfToken);
        }
    }

    invalidate() {
        if (this.apiClient) {
            this.apiClient.invalidate();
        }
    }

    getAbsoluteUrl(baseUrl?: string) {
        if (typeof baseUrl !== 'string' || !baseUrl.startsWith('/')) {
            return baseUrl;
        }
        return this.apiClient.baseUrl + baseUrl;
    }

    getRequestHeaders(requestMethod: string) {
        const headers = {...this.requestHeaders};

        if (this.csrfToken && requestMethod.toLowerCase() !== 'get') {
            headers[ClientConstants.HEADER_X_CSRF_TOKEN] = this.csrfToken;
        }

        return headers;
    }

    getWebSocketUrl = () => {
        return `${this.urlVersion}/websocket`;
    };

    setAcceptLanguage(locale: string) {
        this.requestHeaders[ClientConstants.HEADER_ACCEPT_LANGUAGE] = locale;
    }

    setBearerToken(bearerToken: string) {
        this.requestHeaders[ClientConstants.HEADER_AUTH] = `${ClientConstants.HEADER_BEARER} ${bearerToken}`;
        setServerCredentials(this.apiClient.baseUrl, bearerToken);
    }

    setCSRFToken(csrfToken: string) {
        this.csrfToken = csrfToken;
    }

    // Routes

    getUsersRoute() {
        return `${this.urlVersion}/users`;
    }

    getUserRoute(userId: string) {
        return `${this.getUsersRoute()}/${userId}`;
    }

    getTeamsRoute() {
        return `${this.urlVersion}/teams`;
    }

    getTeamRoute(teamId: string) {
        return `${this.getTeamsRoute()}/${teamId}`;
    }

    getTeamNameRoute(teamName: string) {
        return `${this.getTeamsRoute()}/name/${teamName}`;
    }

    getTeamMembersRoute(teamId: string) {
        return `${this.getTeamRoute(teamId)}/members`;
    }

    getTeamMemberRoute(teamId: string, userId: string) {
        return `${this.getTeamMembersRoute(teamId)}/${userId}`;
    }

    getCategoriesRoute(userId: string, teamId: string) {
        return `${this.getUserRoute(userId)}/teams/${teamId}/channels/categories`;
    }

    getCategoriesOrderRoute(userId: string, teamId: string) {
        return `${this.getCategoriesRoute(userId, teamId)}/order`;
    }

    getCategoryRoute(userId: string, teamId: string, categoryId: string) {
        return `${this.getCategoriesRoute(userId, teamId)}/${categoryId}`;
    }

    getChannelsRoute() {
        return `${this.urlVersion}/channels`;
    }

    getChannelRoute(channelId: string) {
        return `${this.getChannelsRoute()}/${channelId}`;
    }

    getSharedChannelsRoute() {
        return `${this.urlVersion}/sharedchannels`;
    }

    getChannelMembersRoute(channelId: string) {
        return `${this.getChannelRoute(channelId)}/members`;
    }

    getChannelMemberRoute(channelId: string, userId: string) {
        return `${this.getChannelMembersRoute(channelId)}/${userId}`;
    }

    getPostsRoute() {
        return `${this.urlVersion}/posts`;
    }

    getPostRoute(postId: string) {
        return `${this.getPostsRoute()}/${postId}`;
    }

    getReactionsRoute() {
        return `${this.urlVersion}/reactions`;
    }

    getCommandsRoute() {
        return `${this.urlVersion}/commands`;
    }

    getFilesRoute() {
        return `${this.urlVersion}/files`;
    }

    getFileRoute(fileId: string) {
        return `${this.getFilesRoute()}/${fileId}`;
    }

    getPreferencesRoute(userId: string) {
        return `${this.getUserRoute(userId)}/preferences`;
    }

    getEmojisRoute() {
        return `${this.urlVersion}/emoji`;
    }

    getEmojiRoute(emojiId: string) {
        return `${this.getEmojisRoute()}/${emojiId}`;
    }

    getDataRetentionRoute() {
        return `${this.urlVersion}/data_retention`;
    }

    getRolesRoute() {
        return `${this.urlVersion}/roles`;
    }

    getTimezonesRoute() {
        return `${this.urlVersion}/system/timezones`;
    }

    getRedirectLocationRoute() {
        return `${this.urlVersion}/redirect_location`;
    }

    getThreadsRoute(userId: string, teamId: string): string {
        return `${this.getUserRoute(userId)}/teams/${teamId}/threads`;
    }

    getThreadRoute(userId: string, teamId: string, threadId: string): string {
        return `${this.getThreadsRoute(userId, teamId)}/${threadId}`;
    }

    getPluginsRoute() {
        return `${this.urlVersion}/plugins`;
    }

    getPluginRoute(id: string) {
        return `/plugins/${id}`;
    }

    getAppsProxyRoute() {
        return this.getPluginRoute('com.mattermost.apps');
    }

    getCallsRoute() {
        return this.getPluginRoute(Calls.PluginId);
    }

    doFetch = async (url: string, options: ClientOptions, returnDataOnly = true) => {
        let request;
        const method = options.method?.toLowerCase();
        switch (method) {
            case 'get':
                request = this.apiClient!.get;
                break;
            case 'put':
                request = this.apiClient!.put;
                break;
            case 'post':
                request = this.apiClient!.post;
                break;
            case 'patch':
                request = this.apiClient!.patch;
                break;
            case 'delete':
                request = this.apiClient!.delete;
                break;
            default:
                throw new ClientError(this.apiClient.baseUrl, {
                    message: 'Invalid request method',
                    intl: {
                        id: t('mobile.request.invalid_request_method'),
                        defaultMessage: 'Invalid request method',
                    },
                    url,
                });
        }

        const requestOptions: RequestOptions = {
            body: options.body,
            headers: this.getRequestHeaders(method),
        };
        if (options.noRetry) {
            requestOptions.retryPolicyConfiguration = {
                retryLimit: 0,
            };
        }
        if (options.timeoutInterval) {
            requestOptions.timeoutInterval = options.timeoutInterval;
        }

        if (options.headers) {
            if (requestOptions.headers) {
                requestOptions.headers = {
                    ...requestOptions.headers,
                    ...options.headers,
                };
            } else {
                requestOptions.headers = options.headers;
            }
        }

        let response: ClientResponse;
        try {
            response = await request!(url, requestOptions);
        } catch (error) {
            throw new ClientError(this.apiClient.baseUrl, {
                message: 'Received invalid response from the server.',
                intl: {
                    id: t('mobile.request.invalid_response'),
                    defaultMessage: 'Received invalid response from the server.',
                },
                url,
            });
        }

        const headers: ClientHeaders = response.headers || {};
        const serverVersion = semverFromServerVersion(headers[ClientConstants.HEADER_X_VERSION_ID] || headers[ClientConstants.HEADER_X_VERSION_ID.toLowerCase()]);
        const hasCacheControl = Boolean(headers[ClientConstants.HEADER_CACHE_CONTROL] || headers[ClientConstants.HEADER_CACHE_CONTROL.toLowerCase()]);
        if (serverVersion && !hasCacheControl && this.serverVersion !== serverVersion) {
            this.serverVersion = serverVersion;
            DeviceEventEmitter.emit(Events.SERVER_VERSION_CHANGED, {serverUrl: this.apiClient.baseUrl, serverVersion});
        }

        const bearerToken = headers[ClientConstants.HEADER_TOKEN] || headers[ClientConstants.HEADER_TOKEN.toLowerCase()];
        if (bearerToken) {
            this.setBearerToken(bearerToken);
        }

        if (response.ok) {
            return returnDataOnly ? response.data : response;
        }

        throw new ClientError(this.apiClient.baseUrl, {
            message: response.data?.message || '',
            server_error_id: response.data?.id,
            status_code: response.code,
            url,
        });
    };
}
