/**
 * Turnstile 内置求解：等待 widget 自动完成 + token 轮询 + 多 frame sitekey 提取
 *
 * 支持新版 challenge-platform iframe：sitekey 在 URL 路径 /rch/{id}/{sitekey}/ 中
 */
const { detectCloudflare, isTurnstileSolved, urlIndicatesCfChallenge } = require('./cf-detector');

/**
 * 从 challenge-platform / Turnstile URL 解析 sitekey（Node 侧，可读 frame.url()）
 * @param {string} url
 * @returns {string|null}
 */
function extractSitekeyFromChallengeUrl(url) {
    if (!url || typeof url !== 'string') {
        return null;
    }
    const pathMatch = url.match(/\/rch\/[^/]+\/(0x4[A-Za-z0-9_-]+)\//i);
    if (pathMatch) {
        return pathMatch[1];
    }
    const queryMatch = url.match(/[?&]sitekey=([^&]+)/i);
    if (queryMatch) {
        return decodeURIComponent(queryMatch[1]);
    }
    const looseMatch = url.match(/\/(0x4[A-Za-z0-9_-]{10,})\//i);
    if (looseMatch) {
        return looseMatch[1];
    }
    return null;
}

/**
 * 在单个 frame 内提取 sitekey（主文档 DOM + iframe src 属性）
 * @param {import('puppeteer').Frame} frame
 * @returns {Promise<string|null>}
 */
async function extractSitekeyFromFrame(frame) {
    return frame.evaluate(() => {
        const extractFromSrc = (src) => {
            if (!src) return null;
            const pathMatch = src.match(/\/rch\/[^/]+\/(0x4[A-Za-z0-9_-]+)\//i);
            if (pathMatch) return pathMatch[1];
            const queryMatch = src.match(/[?&]sitekey=([^&]+)/i);
            if (queryMatch) return decodeURIComponent(queryMatch[1]);
            const looseMatch = src.match(/\/(0x4[A-Za-z0-9_-]{10,})\//i);
            if (looseMatch) return looseMatch[1];
            return null;
        };

        const widget = document.querySelector('.cf-turnstile[data-sitekey], [data-sitekey]');
        if (widget) {
            const key = widget.getAttribute('data-sitekey');
            if (key) {
                return key;
            }
        }

        const iframes = document.querySelectorAll(
            'iframe[src*="challenges.cloudflare.com"], iframe[src*="challenge-platform"], iframe#cf-chl-widget'
        );
        for (const iframe of iframes) {
            const fromSrc = extractFromSrc(iframe.getAttribute('src') || '');
            if (fromSrc) {
                return fromSrc;
            }
        }

        const scripts = document.querySelectorAll('script');
        for (const script of scripts) {
            const text = script.textContent || '';
            const match = text.match(/sitekey['"\s:=]+['"]([0-9x_a-zA-Z-]{10,})['"]/i)
                || text.match(/data-sitekey=['"]([0-9x_a-zA-Z-]{10,})['"]/i)
                || text.match(/\/(0x4[A-Za-z0-9_-]{10,})\//i);
            if (match) {
                return match[1];
            }
        }

        return null;
    }).catch(() => null);
}

/**
 * 从整页 HTML 文本提取 sitekey（应对 OOPIF 无法 enumerate 的场景）
 * @param {string} html
 * @returns {string|null}
 */
function extractSitekeyFromHtml(html) {
    if (!html || typeof html !== 'string') {
        return null;
    }
    const pathMatch = html.match(/\/rch\/[^/]+\/(0x4[A-Za-z0-9_-]+)\//i);
    if (pathMatch) {
        return pathMatch[1];
    }
    const dataMatch = html.match(/data-sitekey=["']([^"']+)["']/i);
    if (dataMatch) {
        return dataMatch[1];
    }
    const queryMatch = html.match(/[?&]sitekey=([^&"']+)/i);
    if (queryMatch) {
        return decodeURIComponent(queryMatch[1]);
    }
    const looseMatch = html.match(/\/(0x4[A-Za-z0-9_-]{10,})\//i);
    if (looseMatch) {
        return looseMatch[1];
    }
    return null;
}

/**
 * 从主文档及所有子 frame 提取 Turnstile sitekey
 * @param {import('puppeteer').Page} page
 * @returns {Promise<string|null>}
 */
async function extractTurnstileSitekey(page) {
    const mainKey = await extractSitekeyFromFrame(page.mainFrame());
    if (mainKey) {
        return mainKey;
    }

    for (const frame of page.frames()) {
        const fromFrameUrl = extractSitekeyFromChallengeUrl(frame.url());
        if (fromFrameUrl) {
            return fromFrameUrl;
        }
        if (frame === page.mainFrame()) {
            continue;
        }
        const frameKey = await extractSitekeyFromFrame(frame);
        if (frameKey) {
            return frameKey;
        }
    }

    const html = await page.content().catch(() => '');
    const fromHtml = extractSitekeyFromHtml(html);
    if (fromHtml) {
        return fromHtml;
    }

    const browser = page.browser();
    if (browser) {
        for (const target of browser.targets()) {
            const fromTarget = extractSitekeyFromChallengeUrl(target.url());
            if (fromTarget) {
                return fromTarget;
            }
        }
    }

    return null;
}

/**
 * Captcha API 调用前：等待 Turnstile 挑战面出现或 URL 进入 CF 挑战态
 * @param {import('puppeteer').Page} page
 * @param {number} [timeoutMs]
 * @returns {Promise<string|null>}
 */
async function waitForTurnstileSurface(page, timeoutMs = 20000) {
    const started = Date.now();
    const pollMs = 400;

    while (Date.now() - started < timeoutMs) {
        const sitekey = await extractTurnstileSitekey(page);
        if (sitekey) {
            console.log(`[CF] 已检测到 Turnstile sitekey (${sitekey.slice(0, 12)}...)`);
            return sitekey;
        }

        const url = page.url();
        if (urlIndicatesCfChallenge(url)) {
            await new Promise((r) => setTimeout(r, pollMs));
            continue;
        }

        const state = await detectCloudflare(page);
        if (!state.blocked && state.hasCfClearance) {
            return null;
        }

        const hasSurface = await page.evaluate(() => {
            return !!document.querySelector(
                '.cf-turnstile, [data-sitekey], [name="cf-turnstile-response"], '
                + 'iframe[src*="challenges.cloudflare.com"], iframe[src*="challenge-platform"], '
                + 'iframe[id^="cf-chl-widget"]'
            );
        }).catch(() => false);

        if (hasSurface) {
            await new Promise((r) => setTimeout(r, pollMs));
            continue;
        }

        await new Promise((r) => setTimeout(r, pollMs));
    }

    return null;
}

/**
 * 等待 Turnstile 通过（内置点击 / 自动跳转）
 * @param {import('puppeteer').Page} page
 * @param {{ timeoutMs?: number, pollMs?: number }} [options]
 */
async function waitForTurnstile(page, options = {}) {
    const timeoutMs = options.timeoutMs ?? 60000;
    const pollMs = options.pollMs ?? 500;
    const started = Date.now();

    const initial = await detectCloudflare(page);
    const initialUrlChallenge = urlIndicatesCfChallenge(page.url());

    if ((!initial.blocked && !initialUrlChallenge) || initial.hasCfClearance) {
        if (initial.hasCfClearance && !initialUrlChallenge) {
            return { success: true, stage: 'already_clear', elapsedMs: Date.now() - started };
        }
        if (!initial.blocked && !initialUrlChallenge) {
            return { success: true, stage: 'already_clear', elapsedMs: Date.now() - started };
        }
    }

    while (Date.now() - started < timeoutMs) {
        if (await isTurnstileSolved(page) && !urlIndicatesCfChallenge(page.url())) {
            return { success: true, stage: 'built_in', elapsedMs: Date.now() - started };
        }

        const state = await detectCloudflare(page);
        if (!state.blocked && !urlIndicatesCfChallenge(page.url())) {
            return { success: true, stage: 'built_in', elapsedMs: Date.now() - started };
        }

        await new Promise((r) => setTimeout(r, pollMs));
    }

    return { success: false, stage: 'timeout', elapsedMs: Date.now() - started };
}

/**
 * 等待页面离开 CF 挑战 URL / 挑战 DOM
 * @param {import('puppeteer').Page} page
 * @param {number} timeoutMs
 * @param {number} pollMs
 */
async function waitForChallengeUrlClear(page, timeoutMs = 45000, pollMs = 500) {
    const started = Date.now();
    while (Date.now() - started < timeoutMs) {
        if (!urlIndicatesCfChallenge(page.url())) {
            const state = await detectCloudflare(page);
            if (!state.blocked) {
                return true;
            }
        }
        await new Promise((r) => setTimeout(r, pollMs));
    }
    return false;
}

/**
 * 将 Captcha API 返回的 token 注入页面并尝试触发提交
 * @param {import('puppeteer').Page} page
 * @param {string} token
 */
async function injectTurnstileToken(page, token) {
    await page.evaluate((turnstileToken) => {
        const input = document.querySelector('[name="cf-turnstile-response"]');
        if (input) {
            input.value = turnstileToken;
            input.dispatchEvent(new Event('input', { bubbles: true }));
            input.dispatchEvent(new Event('change', { bubbles: true }));
        }
        const form = document.querySelector('form');
        if (form) {
            form.submit();
        }
    }, token);
}

/**
 * token 注入后等待过盾；若仍停留在挑战页则 reload 干净业务 URL
 * @param {import('puppeteer').Page} page
 * @param {string} cleanUrl
 * @param {(page: import('puppeteer').Page) => Promise<string>} [waitForUrlSettled]
 * @param {number} [timeoutMs]
 */
async function recoverAfterTokenInject(page, cleanUrl, waitForUrlSettled, timeoutMs = 45000) {
    if (await waitForChallengeUrlClear(page, Math.min(timeoutMs, 12000))) {
        return true;
    }

    if (!cleanUrl) {
        return false;
    }

    console.log(`[CF] token 注入后仍处挑战页，reload 干净 URL: ${cleanUrl}`);
    await page.goto(cleanUrl, { waitUntil: 'domcontentloaded', timeout: Math.min(timeoutMs, 60000) }).catch(() => {});
    if (typeof waitForUrlSettled === 'function') {
        await waitForUrlSettled(page).catch(() => {});
    }

    return waitForChallengeUrlClear(page, timeoutMs);
}

module.exports = {
    waitForTurnstile,
    waitForTurnstileSurface,
    extractTurnstileSitekey,
    extractSitekeyFromFrame,
    extractSitekeyFromChallengeUrl,
    extractSitekeyFromHtml,
    injectTurnstileToken,
    waitForChallengeUrlClear,
    recoverAfterTokenInject,
};
