import { HttpsProxyAgent } from 'https-proxy-agent';
import { PacProxyAgent } from 'pac-proxy-agent';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { ProxyAgent } from 'undici';

import { config } from '@/config';
import logger from '@/utils/logger';

import type { MultiProxyResult, ProxyState } from './multi-proxy';
import createMultiProxy from './multi-proxy';
import pacProxy from './pac-proxy';
import unifyProxy from './unify-proxy';

const proxyIsPAC = config.pacUri || config.pacScript;

interface ProxyExport {
    agent: PacProxyAgent<string> | HttpsProxyAgent<string> | SocksProxyAgent | null;
    dispatcher: ProxyAgent | null;
    proxyUri?: string;
    proxyObj: Record<string, any>;
    proxyUrlHandler?: URL | null;
    multiProxy?: MultiProxyResult;
    getCurrentProxy: () => ProxyState | null;
    markProxyFailed: (proxyUri: string) => void;
    getAgentForProxy: (proxyState: ProxyState) => any;
    getDispatcherForProxy: (proxyState: ProxyState) => ProxyAgent | null;
}

let proxyUri: string | undefined;
let proxyObj: Record<string, any> = {};
let proxyUrlHandler: URL | null = null;
let multiProxy: MultiProxyResult | undefined;

const createAgentForProxy = (uri: string, proxyObj: Record<string, any>): any => {
    if (uri.startsWith('http')) {
        return new HttpsProxyAgent(uri, {
            headers: {
                'proxy-authorization': proxyObj?.auth ? `Basic ${proxyObj.auth}` : undefined,
            },
        });
    } else if (uri.startsWith('socks')) {
        return new SocksProxyAgent(uri);
    }
    return null;
};

const createDispatcherForProxy = (uri: string, proxyObj: Record<string, any>): ProxyAgent | null => {
    if (uri.startsWith('http')) {
        return new ProxyAgent({
            uri,
            token: proxyObj?.auth ? `Basic ${proxyObj.auth}` : undefined,
            requestTls: {
                rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0',
            },
        });
    }
    return null;
};

if (proxyIsPAC) {
    const proxy = pacProxy(config.pacUri, config.pacScript, config.proxy);
    proxyUri = proxy.proxyUri;
    proxyObj = proxy.proxyObj;
    proxyUrlHandler = proxy.proxyUrlHandler;
} else if (config.proxyUris && config.proxyUris.length > 0) {
    multiProxy = createMultiProxy(config.proxyUris, config.proxy);
    proxyObj = multiProxy.proxyObj;
    const currentProxy = multiProxy.getNextProxy();
    if (currentProxy) {
        proxyUri = currentProxy.uri;
        proxyUrlHandler = currentProxy.urlHandler;
    }
    logger.info(`Multi-proxy initialized with ${config.proxyUris.length} proxies`);
} else {
    const proxy = unifyProxy(config.proxyUri, config.proxy);
    proxyUri = proxy.proxyUri;
    proxyObj = proxy.proxyObj;
    proxyUrlHandler = proxy.proxyUrlHandler;
}

let agent: PacProxyAgent<string> | HttpsProxyAgent<string> | SocksProxyAgent | null = null;
let dispatcher: ProxyAgent | null = null;

if (proxyIsPAC && proxyUri) {
    agent = new PacProxyAgent(`pac+${proxyUri}`);
} else if (proxyUri) {
    agent = createAgentForProxy(proxyUri, proxyObj);
    dispatcher = createDispatcherForProxy(proxyUri, proxyObj);
}

const getCurrentProxy = (): ProxyState | null => {
    if (multiProxy) {
        return multiProxy.getNextProxy();
    }
    if (proxyUri) {
        return {
            uri: proxyUri,
            isActive: true,
            failureCount: 0,
            urlHandler: proxyUrlHandler,
        };
    }
    return null;
};

const markProxyFailed = (failedProxyUri: string) => {
    if (multiProxy) {
        multiProxy.markProxyFailed(failedProxyUri);
        const nextProxy = multiProxy.getNextProxy();
        if (nextProxy) {
            proxyUri = nextProxy.uri;
            proxyUrlHandler = nextProxy.urlHandler || null;
            agent = createAgentForProxy(nextProxy.uri, proxyObj);
            dispatcher = createDispatcherForProxy(nextProxy.uri, proxyObj);
            logger.info(`Switched to proxy: ${nextProxy.uri}`);
        } else {
            logger.warn('No available proxies remaining');
            agent = null;
            dispatcher = null;
            proxyUri = undefined;
        }
    }
};

const getAgentForProxy = (proxyState: ProxyState) => createAgentForProxy(proxyState.uri, proxyObj);

const getDispatcherForProxy = (proxyState: ProxyState) => createDispatcherForProxy(proxyState.uri, proxyObj);

const proxyExport: ProxyExport = {
    agent,
    dispatcher,
    proxyUri,
    proxyObj,
    proxyUrlHandler,
    multiProxy,
    getCurrentProxy,
    markProxyFailed,
    getAgentForProxy,
    getDispatcherForProxy,
};

export default proxyExport;
