import {isObject, isString} from 'lodash';

import {
    CONNECTIONS_DASHSQL,
    CONNECTIONS_TYPED_QUERY_RAW_URL,
    CONNECTION_ID_PLACEHOLDER,
    DATASET_DISTINCTS_URL,
    DATASET_FIELDS_URL,
    DATASET_ID_PLACEHOLDER,
    DATASET_RESULT_URL,
} from '../../../../modes/charts/plugins/control/url/constants';
import {config as configConstants} from '../../constants';
import type {
    APIConnectorParams,
    Source,
    SourceWithAPIConnector,
    SourceWithDatasetId,
    SourceWithQLConnector,
} from '../../types';

const validateAPIConnectorSource = (source: Source): true => {
    const requiredFields = [
        {field: 'apiConnectionId', valid: isString(source.apiConnectionId)},
        {field: 'method', valid: isString(source.method)},
        {field: 'path', valid: isString(source.path)},
    ];
    const missingFields = requiredFields.filter((item) => !item.valid).map((item) => item.field);

    if (missingFields.length > 0) {
        throw new Error(`Missing or invalid API connector fields: ${missingFields.join(', ')}`);
    }

    return true;
};

export const isAPIConnectorSource = (source: Source): source is SourceWithAPIConnector => {
    return isString(source.apiConnectionId) && validateAPIConnectorSource(source);
};

export const getApiConnectorParamsFromSource = (
    source: SourceWithAPIConnector,
): APIConnectorParams => {
    const originalSource = source._original;
    if (
        !(
            isObject(originalSource) &&
            'method' in originalSource &&
            isString(originalSource.method) &&
            'path' in originalSource &&
            isString(originalSource.path)
        )
    ) {
        throw new Error('ApiConnector source is not prepared');
    }

    const result: APIConnectorParams = {
        method: originalSource.method,
        body: {},
        path: originalSource.path,
    };

    if (originalSource.method === 'POST' && 'body' in originalSource) {
        result.body = originalSource.body;
        result.content_type = isString(originalSource.body)
            ? 'text/plain;charset=utf-8'
            : 'application/json';
    }

    return result;
};

export const prepareSourceWithAPIConnector = (source: SourceWithAPIConnector) => {
    // Only save _original if it doesn't exist yet (avoid overwriting on second call)
    if (!source._original) {
        source._original = {...source};
    }
    const sourceUrl = CONNECTIONS_TYPED_QUERY_RAW_URL.replace(
        CONNECTION_ID_PLACEHOLDER,
        encodeURIComponent(source.apiConnectionId),
    );
    source.url = sourceUrl;
    source.method = 'POST';
    return source;
};

const validateQLConnectionSource = (source: Source): true => {
    const requiredFields = [
        {field: 'qlConnectionId', valid: isString(source.qlConnectionId)},
        {field: 'data', valid: isObject(source.data)},
    ];
    const missingFields = requiredFields.filter((item) => !item.valid).map((item) => item.field);

    if (missingFields.length > 0) {
        throw new Error(`Missing or invalid QL connector fields: ${missingFields.join(', ')}`);
    }

    return true;
};

export const isQLConnectionSource = (source: Source): source is SourceWithQLConnector => {
    return isString(source.qlConnectionId) && validateQLConnectionSource(source);
};

export const prepareSourceWithQLConnection = (source: SourceWithQLConnector) => {
    const sourceUrl = CONNECTIONS_DASHSQL.replace(
        CONNECTION_ID_PLACEHOLDER,
        encodeURIComponent(source.qlConnectionId),
    );
    source.url = sourceUrl;
    source.method = 'POST';
    return source;
};

export const isDatasetSource = (source: Source): source is SourceWithDatasetId => {
    return isString(source.datasetId);
};

export const prepareSourceWithDataset = (source: SourceWithDatasetId) => {
    const urlPath =
        isObject(source) && 'path' in source && isString(source.path) ? source.path : 'result';

    let template: string;
    let method: 'POST' | 'GET';
    switch (urlPath) {
        case 'result': {
            template = DATASET_RESULT_URL;
            method = 'POST';
            break;
        }
        case 'values/distinct': {
            template = DATASET_DISTINCTS_URL;
            method = 'POST';
            break;
        }
        case 'fields': {
            template = DATASET_FIELDS_URL;
            method = 'GET';
            break;
        }
        default: {
            throw Error('Wrong path');
        }
    }

    const sourceUrl = template.replace(
        DATASET_ID_PLACEHOLDER,
        encodeURIComponent(source.datasetId),
    );

    source.url = sourceUrl;
    source.method = method;
    return source;
};

export const prepareSource = (source: Source): Source => {
    if (!isObject(source)) {
        return source;
    }

    if (isAPIConnectorSource(source)) {
        return prepareSourceWithAPIConnector(source);
    }

    if (isQLConnectionSource(source)) {
        return prepareSourceWithQLConnection(source);
    }

    if (isDatasetSource(source)) {
        return prepareSourceWithDataset(source);
    }

    return source;
};

const {
    DEFAULT_SOURCE_FETCHING_ERROR_STATUS_400,
    DEFAULT_SOURCE_FETCHING_ERROR_STATUS_500,
    DEFAULT_SOURCE_FETCHING_LIMIT_EXCEEDED_STATUS,
    REQUEST_SIZE_LIMIT_EXCEEDED,
    ALL_REQUESTS_SIZE_LIMIT_EXCEEDED,
} = configConstants;

export function getSourcesErrorStatusCode(error: object) {
    let maybe400 = false;
    let maybe500 = false;
    let requestSizeLimitExceeded = false;
    Object.values(error).forEach((sourceResult) => {
        const possibleStatus = sourceResult?.status;

        if (399 < possibleStatus && possibleStatus < 500) {
            maybe400 = true;
        } else {
            maybe500 = true;
        }

        if (
            sourceResult.code === REQUEST_SIZE_LIMIT_EXCEEDED ||
            sourceResult.code === ALL_REQUESTS_SIZE_LIMIT_EXCEEDED
        ) {
            requestSizeLimitExceeded = true;
        }
    });

    if (maybe400 && !maybe500) {
        return DEFAULT_SOURCE_FETCHING_ERROR_STATUS_400;
    }

    if (requestSizeLimitExceeded) {
        return DEFAULT_SOURCE_FETCHING_LIMIT_EXCEEDED_STATUS;
    }

    return DEFAULT_SOURCE_FETCHING_ERROR_STATUS_500;
}
