/**
 * E2E Tests for Chat Mode Welcome-Screen Suggestion Chips
 *
 * The four chips on the welcome screen (chat.html:44-56) each carry a
 * `data-query` attribute. Clicking a chip pre-fills the textarea and
 * calls handleSend() (chat.js), guarded against double-fire by
 * `isProcessing`.
 *
 * Contract verified here:
 *   - All four chips render and have non-empty data-query values.
 *   - Clicking a chip starts a chat: welcome screen hides and a user
 *     message bubble appears with the chip's query text.
 *   - The URL transitions from /chat/ to /chat/<session_id>.
 *
 * No LLM required: we stop as soon as the user-side message has been
 * rendered. We do not wait for the assistant response.
 *
 * Prerequisites: Web server on http://127.0.0.1:5000 (override BASE_URL).
 */

const puppeteer = require('puppeteer');
const AuthHelper = require('../auth_helper');
const { getPuppeteerLaunchOptions } = require('../puppeteer_config');
const fs = require('fs');
const path = require('path');

const BASE_URL = process.env.BASE_URL || 'http://127.0.0.1:5000';
const isCI = !!process.env.CI;

const TIMEOUTS = {
    navigation: isCI ? 60000 : 30000,
    selector: isCI ? 30000 : 10000,
    element: isCI ? 10000 : 5000,
};

const SCREENSHOTS_DIR = path.join(__dirname, '..', 'screenshots');

async function snap(page, name) {
    if (!fs.existsSync(SCREENSHOTS_DIR)) {
        fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
    }
    const file = path.join(SCREENSHOTS_DIR, `chat_chips_${name}_${Date.now()}.png`);
    try {
        await page.screenshot({ path: file, fullPage: true });
    } catch (_) {}
}

async function gotoChat(page) {
    await page.goto(`${BASE_URL}/chat/`, {
        waitUntil: 'domcontentloaded',
        timeout: TIMEOUTS.navigation,
    });
    await page.waitForSelector('.ldr-chat-container', { timeout: TIMEOUTS.selector });
    // Wait for chat.js init() to fully settle, INCLUDING the async
    // restore of the most-recent session. A bare /chat/ visit
    // auto-resumes the last session, which hides the welcome screen and
    // detaches the suggestion chips — so racing the chips here yields a
    // "Node is not clickable" error. Once init-complete is set, force a
    // clean New Chat state so the welcome chips are reliably present and
    // clickable regardless of what earlier tests in the shard left
    // behind.
    await page.waitForSelector('#chat-input[data-init-complete="true"]', { timeout: TIMEOUTS.selector });
    await page.evaluate(() => window.chatComponent.startNewChat());
    await page.waitForFunction(
        () => {
            const el = document.getElementById('chat-welcome');
            return el && getComputedStyle(el).display !== 'none';
        },
        { timeout: TIMEOUTS.selector }
    );
    await page.waitForSelector('.ldr-chat-suggestion', { timeout: TIMEOUTS.selector });
}

async function run() {
    console.log(`Running chat suggestion-chip tests (CI mode: ${isCI})`);

    const browser = await puppeteer.launch(getPuppeteerLaunchOptions());
    const page = await browser.newPage();
    await page.setViewport({ width: 1280, height: 800 });
    if (isCI) {
        page.setDefaultTimeout(60000);
        page.setDefaultNavigationTimeout(60000);
    }

    page.on('pageerror', (e) => console.log('PAGE ERROR:', e.message));
    page.on('console', (m) => {
        if (m.type() === 'error') console.log('BROWSER ERROR:', m.text());
    });

    const auth = new AuthHelper(page, BASE_URL);

    let passed = 0;
    let failed = 0;

    try {
        await auth.ensureAuthenticated();

        // Test 1: Four chips render, each with a non-empty data-query
        console.log('Test 1: Four suggestion chips render with data-query');
        try {
            await gotoChat(page);
            const chips = await page.$$eval('.ldr-chat-suggestion', (els) =>
                els.map((e) => ({
                    text: (e.textContent || '').trim(),
                    query: e.dataset.query || '',
                }))
            );
            if (chips.length !== 4) {
                throw new Error(`Expected 4 chips, got ${chips.length}`);
            }
            for (const c of chips) {
                if (!c.text) throw new Error('Chip has empty label');
                if (!c.query) throw new Error(`Chip "${c.text}" has empty data-query`);
            }
            console.log('PASSED — chips:', chips.map((c) => c.text).join(' | '));
            passed++;
        } catch (e) {
            console.log(`FAILED: ${e.message}`);
            await snap(page, 'render');
            failed++;
        }

        // Test 2: Clicking a chip starts a chat and surfaces the user message
        console.log('Test 2: Clicking a chip sends its query as a user message');
        try {
            await gotoChat(page);
            const firstQuery = await page.$eval(
                '.ldr-chat-suggestion',
                (b) => b.dataset.query
            );
            if (!firstQuery) throw new Error('First chip missing data-query');

            // Real click for genuine interaction coverage. CDP input used
            // to be swallowed after login by Chrome's password-leak dialog
            // (#4430); that is disabled via chrome_profile.js now.
            await page.click('.ldr-chat-suggestion');

            // Welcome screen hides (chat.js inside handleSend)
            await page.waitForFunction(
                () => {
                    const el = document.getElementById('chat-welcome');
                    return !el || getComputedStyle(el).display === 'none';
                },
                { timeout: TIMEOUTS.selector }
            );

            // User-bubble with the chip's query appears
            await page.waitForFunction(
                (needle) => {
                    const nodes = document.querySelectorAll(
                        '.ldr-chat-message-user .ldr-chat-message-text'
                    );
                    return Array.from(nodes).some((n) =>
                        (n.textContent || '').includes(needle)
                    );
                },
                { timeout: TIMEOUTS.selector },
                firstQuery
            );

            // URL moves from /chat/ -> /chat/<session> (createSession + pushState, chat.js)
            await page.waitForFunction(
                () => /\/chat\/[\w-]+/.test(window.location.pathname),
                { timeout: TIMEOUTS.selector }
            );

            console.log('PASSED');
            passed++;
        } catch (e) {
            console.log(`FAILED: ${e.message}`);
            await snap(page, 'click');
            failed++;
        }

        // Test 3: Once isProcessing fires, additional chip clicks are no-ops
        // chat.js — `if (isProcessing) return;`
        console.log('Test 3: Subsequent chip clicks while processing are ignored');
        try {
            await gotoChat(page);

            // First click — starts a chat.
            await page.click('.ldr-chat-suggestion');
            // Wait until the first user message is in the DOM
            await page.waitForFunction(
                () =>
                    document.querySelectorAll(
                        '.ldr-chat-message-user .ldr-chat-message-text'
                    ).length >= 1,
                { timeout: TIMEOUTS.selector }
            );

            // Click another chip — should be a no-op because isProcessing is
            // true. JS .click() here on purpose: the welcome screen (and the
            // chips) are already hidden at this point, so a real page.click
            // would fail on a non-visible node; the guard under test is the
            // handler's isProcessing check, not input delivery.
            await page.evaluate(() => {
                const chips = document.querySelectorAll('.ldr-chat-suggestion');
                if (chips[1]) chips[1].click();
            });

            // Wait for the network to settle. The first chip click fired a
            // POST /api/chat/...; once that completes and no new requests
            // appear for 500ms, the synchronous second-chip handler has
            // had every opportunity to (incorrectly) add a bubble. This
            // is deterministic, unlike a wall-clock setTimeout.
            await page
                .waitForNetworkIdle({ idleTime: 500, timeout: TIMEOUTS.element })
                .catch(() => {});

            const userBubbleCount = await page.$$eval(
                '.ldr-chat-message-user .ldr-chat-message-text',
                (els) => els.length
            );
            if (userBubbleCount !== 1) {
                throw new Error(
                    `Expected exactly 1 user bubble, got ${userBubbleCount} ` +
                        '(second chip click should have been blocked by isProcessing)'
                );
            }
            console.log('PASSED');
            passed++;
        } catch (e) {
            console.log(`FAILED: ${e.message}`);
            await snap(page, 'guard');
            failed++;
        }
    } catch (e) {
        console.log(`Test suite error: ${e.message}`);
        failed++;
    } finally {
        await browser.close();
    }

    console.log('-'.repeat(50));
    console.log(`Chat Suggestion Chip Tests — passed: ${passed}, failed: ${failed}`);
    console.log('-'.repeat(50));
    if (failed > 0) process.exit(1);
}

run().catch((e) => {
    console.error('Test runner error:', e);
    process.exit(1);
});
