import type { CorsOptions } from 'cors';
import type { Application } from 'express';
import type {
    ClientFeaturesResponse,
    Strategy,
    TagFilter,
} from 'unleash-client';
import type { HttpOptions } from 'unleash-client/lib/http-options';
import type { BootstrapOptions } from 'unleash-client/lib/repository/bootstrap-provider';
import type { StorageProvider } from 'unleash-client/lib/repository/storage-provider';
import type { ContextEnricher } from './enrich-context';
import { type LogLevel, type Logger, SimpleLogger } from './logger';
import { generateInstanceId } from './util';

export interface ServerSideSdkConfig {
    tokens: string[];
}
export interface IProxyOption {
    unleashUrl?: string;
    unleashApiToken?: string;
    unleashAppName?: string;
    unleashInstanceId?: string;
    customStrategies?: Strategy[];
    proxySecrets?: string[];
    clientKeys?: string[];
    preHook?: (app: Application) => void;
    proxyBasePath?: string;
    refreshInterval?: number;
    metricsInterval?: number;
    metricsJitter?: number;
    environment?: string;
    projectName?: string;
    logger?: Logger;
    useJsonLogger?: boolean;
    logLevel?: LogLevel;
    trustProxy?: boolean | string | number;
    namePrefix?: string;
    tags?: Array<TagFilter>;
    clientKeysHeaderName?: string;
    enableOAS?: boolean;
    cors?: CorsOptions;
    enableAllEndpoint?: boolean;
    storageProvider?: StorageProvider<ClientFeaturesResponse>;
    // experimental options
    expBootstrap?: BootstrapOptions;
    expServerSideSdkConfig?: ServerSideSdkConfig;
    httpOptions?: HttpOptions;
    expCustomEnrichers?: ContextEnricher[];
    clientMode?: 'singleton' | 'new';
}

export interface IProxyConfig {
    unleashUrl: string;
    unleashApiToken: string;
    unleashAppName: string;
    unleashInstanceId: string;
    customStrategies?: Strategy[];
    clientKeys: string[];
    proxyBasePath: string;
    refreshInterval: number;
    metricsInterval: number;
    metricsJitter: number;
    environment?: string;
    projectName?: string;
    logger: Logger;
    disableMetrics: boolean;
    trustProxy: boolean | string | number;
    namePrefix?: string;
    tags?: Array<TagFilter>;
    enableOAS: boolean;
    enableAllEndpoint?: boolean;
    clientKeysHeaderName: string;
    serverSideSdkConfig?: ServerSideSdkConfig;
    bootstrap?: BootstrapOptions;
    cors: CorsOptions;
    httpOptions?: HttpOptions;
    storageProvider?: StorageProvider<ClientFeaturesResponse>;
    expCustomEnrichers?: ContextEnricher[];
}

function resolveStringToArray(value?: string): string[] | undefined {
    if (value) {
        return value.split(/,\s*/);
    }
    return undefined;
}

function safeNumber(envVar: string | undefined, defaultVal: number): number {
    if (envVar) {
        try {
            return Number.parseInt(envVar, 10);
        } catch (err) {
            return defaultVal;
        }
    } else {
        return defaultVal;
    }
}

function safeBoolean(envVar: string | undefined, defaultVal: boolean): boolean {
    if (envVar) {
        return envVar === 'true' || envVar === '1' || envVar === 't';
    }
    return defaultVal;
}

function loadCustomStrategies(path?: string): Strategy[] | undefined {
    if (path) {
        // eslint-disable-next-line
        const strategies = require(path) as Strategy[];
        return strategies;
    }
    return undefined;
}
function removeTrailingPath(path: string): string {
    return path.endsWith('/') ? path.slice(0, -1) : path;
}

function addLeadingPath(path: string): string {
    return path.startsWith('/') ? path : `/${path}`;
}

export function sanitizeBasePath(path?: string): string {
    if (path === null || path === undefined || path.trim() === '') {
        return '';
    }
    return removeTrailingPath(addLeadingPath(path.trim()));
}

function loadCustomEnrichers(path?: string): ContextEnricher[] | undefined {
    if (path) {
        // eslint-disable-next-line
        const contextEnrichers = require(path) as ContextEnricher[];
        return contextEnrichers;
    }
    return undefined;
}

function loadTrustProxy(value: string = 'FALSE') {
    const upperValue = value.toUpperCase();
    if (upperValue === 'FALSE') {
        return false;
    }
    if (upperValue === 'TRUE') {
        return true;
    }
    return value;
}

function mapTagsToFilters(tags?: string): Array<TagFilter> | undefined {
    return resolveStringToArray(tags)?.map((tag) => {
        const [name, value] = tag.split(':');
        return { name, value };
    });
}

function loadClientKeys(option: IProxyOption): string[] | undefined {
    return (
        option.clientKeys ||
        resolveStringToArray(process.env.UNLEASH_PROXY_CLIENT_KEYS) ||
        option.proxySecrets ||
        resolveStringToArray(process.env.UNLEASH_PROXY_SECRETS)
    );
}

function loadServerSideSdkConfig(
    option: IProxyOption,
): ServerSideSdkConfig | undefined {
    if (option.expServerSideSdkConfig) {
        return option.expServerSideSdkConfig;
    }
    const tokens = resolveStringToArray(
        process.env.EXP_SERVER_SIDE_SDK_CONFIG_TOKENS,
    );
    return tokens ? { tokens } : undefined;
}

function loadBootstrapOptions(
    option: IProxyOption,
): BootstrapOptions | undefined {
    if (option.expBootstrap) {
        return option.expBootstrap;
    }
    const bootstrapUrl = process.env.EXP_BOOTSTRAP_URL;
    const expBootstrapAuthorization = process.env.EXP_BOOTSTRAP_AUTHORIZATION;

    const headers = expBootstrapAuthorization
        ? { Authorization: expBootstrapAuthorization }
        : undefined;

    if (bootstrapUrl) {
        return {
            url: bootstrapUrl,
            urlHeaders: headers,
        };
    }
    return undefined;
}

function loadCorsOptions(option: IProxyOption): CorsOptions {
    if (option.cors) {
        return option.cors;
    }

    const computedCorsOptions: CorsOptions = {
        origin: process.env.CORS_ORIGIN || '*',
        methods: process.env.CORS_METHODS || 'GET, POST',
        allowedHeaders: process.env.CORS_ALLOWED_HEADERS,
        exposedHeaders: process.env.CORS_EXPOSED_HEADERS || 'ETag',
        credentials: safeBoolean(process.env.CORS_CREDENTIALS, false),
        maxAge: safeNumber(process.env.CORS_MAX_AGE, 172800),
        preflightContinue: safeBoolean(
            process.env.CORS_PREFLIGHT_CONTINUE,
            false,
        ),
        optionsSuccessStatus: safeNumber(
            process.env.CORS_OPTIONS_SUCCESS_STATUS,
            204,
        ),
    };

    // if cors origin provided contains "," it means it's a list of urls, transform to array
    if (
        typeof computedCorsOptions.origin === 'string' &&
        computedCorsOptions.origin.includes(',')
    ) {
        const transformedOriginList = resolveStringToArray(
            computedCorsOptions.origin,
        );
        if (!transformedOriginList) {
            throw new TypeError(
                `corsOptions.origin (CORS_ORIGIN) unable to transform string to array`,
            );
        }
        computedCorsOptions.origin = transformedOriginList;
    }

    return computedCorsOptions;
}

function loadHttpOptions(option: IProxyOption): IProxyOption {
    if (option.httpOptions) {
        return {
            httpOptions: option.httpOptions,
        };
    }

    if (process.env.HTTP_OPTIONS_REJECT_UNAUTHORIZED) {
        return {
            httpOptions: {
                rejectUnauthorized: safeBoolean(
                    process.env.HTTP_OPTIONS_REJECT_UNAUTHORIZED,
                    true,
                ),
            },
        };
    }

    return {};
}

function chooseLogger(option: IProxyOption): Logger {
    const logLevel = option.logLevel || (process.env.LOG_LEVEL as LogLevel);

    if (option.logger) {
        return option.logger;
    }

    if (option.useJsonLogger || process.env.JSON_LOGGER) {
        return new SimpleLogger(logLevel, true);
    }

    return new SimpleLogger(logLevel);
}

export function createProxyConfig(option: IProxyOption): IProxyConfig {
    const unleashUrl = option.unleashUrl || process.env.UNLEASH_URL;
    if (!unleashUrl) {
        throw new TypeError(
            'You must specify the unleashUrl option (UNLEASH_URL)',
        );
    }

    const unleashApiToken =
        option.unleashApiToken || process.env.UNLEASH_API_TOKEN;
    if (!unleashApiToken) {
        throw new TypeError(
            'You must specify the unleashApiToken option (UNLEASH_API_TOKEN)',
        );
    }

    const customStrategies =
        option.customStrategies ||
        loadCustomStrategies(process.env.UNLEASH_CUSTOM_STRATEGIES_FILE);

    const customEnrichers =
        option.expCustomEnrichers ||
        loadCustomEnrichers(process.env.EXP_CUSTOM_ENRICHERS_FILE);

    const clientKeys = loadClientKeys(option);
    if (!clientKeys) {
        throw new TypeError(
            'You must specify the clientKeys option (UNLEASH_PROXY_CLIENT_KEYS)',
        );
    }

    const trustProxy =
        option.trustProxy || loadTrustProxy(process.env.TRUST_PROXY);

    const tags = option.tags || mapTagsToFilters(process.env.UNLEASH_TAGS);

    const unleashInstanceId =
        option.unleashInstanceId ||
        process.env.UNLEASH_INSTANCE_ID ||
        generateInstanceId();

    const proxyBasePath = sanitizeBasePath(
        option.proxyBasePath || process.env.PROXY_BASE_PATH,
    );
    return {
        unleashUrl,
        unleashApiToken,
        unleashAppName:
            option.unleashAppName ||
            process.env.UNLEASH_APP_NAME ||
            'unleash-proxy',
        unleashInstanceId,
        customStrategies,
        expCustomEnrichers: customEnrichers,
        clientKeys,
        proxyBasePath,
        refreshInterval:
            option.refreshInterval ||
            safeNumber(process.env.UNLEASH_FETCH_INTERVAL, 5_000),
        metricsInterval:
            option.metricsInterval ||
            safeNumber(process.env.UNLEASH_METRICS_INTERVAL, 30_000),
        metricsJitter:
            option.metricsJitter ||
            safeNumber(process.env.UNLEASH_METRICS_JITTER, 0),
        environment: option.environment || process.env.UNLEASH_ENVIRONMENT,
        projectName: option.projectName || process.env.UNLEASH_PROJECT_NAME,
        namePrefix: option.namePrefix || process.env.UNLEASH_NAME_PREFIX,
        storageProvider: option.storageProvider,
        disableMetrics: false,
        logger: chooseLogger(option),
        trustProxy,
        tags,
        clientKeysHeaderName:
            option.clientKeysHeaderName ||
            process.env.CLIENT_KEY_HEADER_NAME ||
            'authorization',
        serverSideSdkConfig: loadServerSideSdkConfig(option),
        bootstrap: loadBootstrapOptions(option),
        enableAllEndpoint:
            option.enableAllEndpoint ||
            safeBoolean(process.env.ENABLE_ALL_ENDPOINT, false),
        enableOAS:
            option.enableOAS || safeBoolean(process.env.ENABLE_OAS, false),
        cors: loadCorsOptions(option),
        ...loadHttpOptions(option),
    };
}
