import { Readable } from 'stream';

interface ProxyHeaders {
    referer?: string;
    origin?: string;
    userAgent?: string;
}

interface ProxyResponse {
    response: Response;
    contentType: string | null;
    isM3U8: boolean;
    contentLength: string | null;
}

export async function proxyRequest(url: string, { referer, origin, userAgent }: ProxyHeaders): Promise<ProxyResponse> {
    const headers: Record<string, string> = {
        'User-Agent': userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        'Accept': '*/*',
        'Accept-Language': 'en-US,en;q=0.9',
        'Accept-Encoding': 'identity',

        'Connection': 'keep-alive'
    };

    if (referer) headers['Referer'] = referer;
    if (origin) headers['Origin'] = origin;

    let lastError: Error | null = null;
    const maxRetries = 2;

    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {

            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), 60000);

            const response = await fetch(url, {
                headers,
                redirect: 'follow',
                signal: controller.signal
            });

            clearTimeout(timeoutId);

            if (!response.ok) {

                if (response.status === 404 || response.status === 403) {
                    throw new Error(`Proxy Error: ${response.status} ${response.statusText}`);
                }

                if (attempt < maxRetries - 1) {
                    await new Promise(resolve => setTimeout(resolve, 500));
                    continue;
                }

                throw new Error(`Proxy Error: ${response.status} ${response.statusText}`);
            }

            const contentType = response.headers.get('content-type');
            const contentLength = response.headers.get('content-length');
            const isM3U8 = (contentType && contentType.includes('mpegurl')) || url.includes('.m3u8');

            return {
                response,
                contentType,
                isM3U8,
                contentLength
            };

        } catch (error) {
            lastError = error as Error;

            if (attempt === maxRetries - 1) {
                throw lastError;
            }

            await new Promise(resolve => setTimeout(resolve, 500));
        }
    }

    throw lastError || new Error('Unknown error in proxyRequest');
}

export function processM3U8Content(text: string, baseUrl: URL, { referer, origin, userAgent }: ProxyHeaders): string {
    return text.replace(/^(?!#)(?!\s*$).+/gm, (line) => {
        line = line.trim();
        let absoluteUrl: string;

        try {
            absoluteUrl = new URL(line, baseUrl).href;
        } catch (e) {
            return line;
        }

        const proxyParams = new URLSearchParams();
        proxyParams.set('url', absoluteUrl);
        if (referer) proxyParams.set('referer', referer);
        if (origin) proxyParams.set('origin', origin);
        if (userAgent) proxyParams.set('userAgent', userAgent);

        return `/api/proxy?${proxyParams.toString()}`;
    });
}

export function streamToReadable(webStream: ReadableStream): Readable {
    const reader = webStream.getReader();
    let readTimeout: NodeJS.Timeout;

    return new Readable({
        async read() {
            try {

                const timeoutPromise = new Promise((_, reject) => {
                    readTimeout = setTimeout(() => reject(new Error('Stream read timeout')), 10000);
                });

                const readPromise = reader.read();
                const { done, value } = await Promise.race([readPromise, timeoutPromise]) as any;

                clearTimeout(readTimeout);

                if (done) {
                    this.push(null);
                } else {
                    this.push(Buffer.from(value));
                }
            } catch (error) {
                clearTimeout(readTimeout);
                this.destroy(error as Error);
            }
        },
        destroy(error, callback) {
            clearTimeout(readTimeout);
            reader.cancel().then(() => callback(error)).catch(callback);
        }
    });
}