import {
    headerHelper,
    IResponse,
    IHeader,
    IHttpService,
    IQueryParameter,
    ISDKInfo,
    urlHelper
} from '@kontent-ai/core-sdk';
import { AxiosError } from 'axios';

import { IManagementClientConfig } from '../config/imanagement-client-config.interface';
import { SharedContracts } from '../contracts';
import { IContentManagementInternalQueryConfig, IContentManagementQueryConfig, SharedModels } from '../models';
import { getType } from 'mime';

export abstract class BaseManagementQueryService<TCancelToken> {
    /**
     * Default base url for content management API
     */
    private readonly defaultBaseCMUrl: string = 'https://manage.kontent.ai/v2';

    constructor(
        protected readonly config: IManagementClientConfig,
        protected readonly httpService: IHttpService<TCancelToken>,
        protected readonly sdkInfo: ISDKInfo
    ) {}

    /**
     * Gets url based on the action, query configuration and options (parameters)
     * @param action Action (= url part) that will be hit
     * @param options Query options
     * @param addSlash Indicates if slash is added to query
     */
    getFullUrl(action: string, options?: IQueryParameter[], addSlash: boolean = true): string {
        return urlHelper.addOptionsToUrl(this.getBaseUrl() + (addSlash ? '/' : '') + action, options);
    }

    /**
     * Gets proper set of headers for given request.
     * @param config Query config
     */
    getHeaders(config: IContentManagementQueryConfig): IHeader[] {
        const headers: IHeader[] = [
            // sdk tracking header
            headerHelper.getSdkIdHeader({
                host: this.sdkInfo.host,
                name: this.sdkInfo.name,
                version: this.sdkInfo.version
            }),
            // add authorization header
            this.getAuthorizationHeader()
        ];

        // add query headers
        headers.push(...config.headers);

        return headers;
    }

    /**
     * Http PATCH response
     * @param url Url of request
     * @param config Query configuration
     */
    protected async patchResponseAsync<TRawData>(
        url: string,
        body: any,
        internalConfig: IContentManagementInternalQueryConfig,
        config: IContentManagementQueryConfig
    ): Promise<IResponse<TRawData>> {
        try {
            return await this.httpService.patchAsync<TRawData>(
                {
                    url: url,
                    body: body
                },
                {
                    cancelToken: config.cancelTokenRequest,
                    retryStrategy: this.config.retryStrategy,
                    headers: this.getHeaders(config),
                    responseType:
                        internalConfig && internalConfig.responseType ? internalConfig.responseType : undefined
                }
            );
        } catch (error) {
            throw this.mapContentManagementError(error);
        }
    }

    /**
     * Http GET response
     * @param url Url of request
     * @param config Query configuration
     */
    protected async getResponseAsync<TRawData>(
        url: string,
        internalConfig: IContentManagementInternalQueryConfig,
        config: IContentManagementQueryConfig
    ): Promise<IResponse<TRawData>> {
        try {
            return await this.httpService.getAsync<TRawData>(
                {
                    url: url
                },
                {
                    cancelToken: config.cancelTokenRequest,
                    retryStrategy: this.config.retryStrategy,
                    headers: this.getHeaders(config),
                    responseType:
                        internalConfig && internalConfig.responseType ? internalConfig.responseType : undefined
                }
            );
        } catch (err) {
            throw this.mapContentManagementError(err);
        }
    }

    /**
     * Http POST response
     * @param url Url of request
     * @param body Body of the request (names and values)
     * @param config Query configuration
     */
    protected async postResponseAsync<TRawData>(
        url: string,
        body: any,
        internalConfig: IContentManagementInternalQueryConfig,
        config: IContentManagementQueryConfig
    ): Promise<IResponse<TRawData>> {
        try {
            return await this.httpService.postAsync<TRawData>(
                {
                    url: url,
                    body: body
                },
                {
                    cancelToken: config.cancelTokenRequest,
                    retryStrategy: this.config.retryStrategy,
                    headers: this.getHeaders(config),
                    responseType:
                        internalConfig && internalConfig.responseType ? internalConfig.responseType : undefined
                }
            );
        } catch (err) {
            throw this.mapContentManagementError(err);
        }
    }

    /**
     * Http PUT response
     * @param url Url of request
     * @param body Body of the request (names and values)
     * @param config Query configuration
     */
    protected async putResponseAsync<TRawData>(
        url: string,
        body: any,
        internalConfig: IContentManagementInternalQueryConfig,
        config: IContentManagementQueryConfig
    ): Promise<IResponse<TRawData>> {
        try {
            return await this.httpService.putAsync<TRawData>(
                {
                    url: url,
                    body: body
                },
                {
                    cancelToken: config.cancelTokenRequest,
                    retryStrategy: this.config.retryStrategy,
                    headers: this.getHeaders(config),
                    responseType:
                        internalConfig && internalConfig.responseType ? internalConfig.responseType : undefined
                }
            );
        } catch (err) {
            throw this.mapContentManagementError(err);
        }
    }

    /**
     * Http Delete response
     * @param url Url of request
     * @param body Body of the request (names and values)
     * @param config Query configuration
     */
    protected async deleteResponseAsync<TRawData>(
        url: string,
        internalConfig: IContentManagementInternalQueryConfig,
        config: IContentManagementQueryConfig
    ): Promise<IResponse<TRawData>> {
        try {
            return await this.httpService.deleteAsync<TRawData>(
                {
                    url: url
                },
                {
                    cancelToken: config.cancelTokenRequest,
                    retryStrategy: this.config.retryStrategy,
                    headers: this.getHeaders(config),
                    responseType:
                        internalConfig && internalConfig.responseType ? internalConfig.responseType : undefined
                }
            );
        } catch (err) {
            throw this.mapContentManagementError(err);
        }
    }

    protected async getBinaryDataFromUrlAsync(url: string): Promise<ArrayBuffer> {
        // temp fix for repository not validating url
        url = url.replace('#', '%23');

        const response = await this.httpService.getAsync<ArrayBuffer>(
            {
                url: url
            },
            {
                responseType: 'arraybuffer'
            }
        );

        return response.data;
    }

    protected getMimeTypeFromFilename(filename: string): string | null {
        return getType(filename);
    }

    private mapContentManagementError(error: any): any {
        let axiosError: AxiosError | undefined;

        if (error.error) {
            axiosError = error.error;
        } else {
            axiosError = error;
        }

        if (!axiosError || !axiosError.isAxiosError) {
            return error;
        }

        const cmError = axiosError.response?.data as SharedContracts.IContentManagementError;

        if (cmError?.error_code || cmError?.request_id) {
            const validationErrors: SharedModels.ValidationError[] = [];

            if (cmError.validation_errors) {
                validationErrors.push(
                    ...cmError.validation_errors.map(
                        (validationErrorRaw) =>
                            new SharedModels.ValidationError({
                                message: validationErrorRaw.message
                            })
                    )
                );
            }

            return new SharedModels.ContentManagementBaseKontentError({
                errorCode: cmError.error_code,
                message: cmError.message,
                originalError: error,
                requestId: cmError.request_id,
                validationErrors: validationErrors
            });
        }

        return error;
    }

    /**
     * Gets authorization header
     */
    private getAuthorizationHeader(): IHeader {
        const key: string = this.config.apiKey;

        if (!key) {
            throw Error(`Cannot get authorization header for query type because API Key is undefined`);
        }

        return {
            header: 'authorization',
            value: `bearer ${key}`
        };
    }
    /**
     * Gets base URL of the request including the project Id
     */
    private getBaseUrl(): string {
        if (this.config.baseUrl) {
            return this.config.baseUrl;
        }
        return this.defaultBaseCMUrl;
    }
}
