/**
 * Shared test helpers for Puppeteer E2E tests
 *
 * This module provides common utilities used across test files:
 * - Screenshot capture
 * - Element interaction helpers
 * - Authentication helpers
 */

const path = require('path');
const fs = require('fs');

// Shared across both E2E suites — plain relative require works
// because both suites live in the same repo checkout.
const { createSeededChromeProfile } = require('../../shared/chrome_profile');

// Test configuration
const BASE_URL = process.env.TEST_URL || 'http://localhost:5000';
const HEADLESS = process.env.HEADLESS !== 'false';
const SLOW_MO = parseInt(process.env.SLOW_MO, 10) || 0;
const CI_FAST_MODE = process.env.CI_FAST_MODE === 'true';

// In CI fast mode, reduce delays by 40% to speed up tests while keeping stability
const DELAY_MULTIPLIER = CI_FAST_MODE ? 0.6 : 1.0;

/**
 * CI-aware delay function - uses shorter delays in CI mode
 * @param {number} ms - Base delay in milliseconds
 * @returns {Promise} Promise that resolves after the adjusted delay
 */
async function delay(ms) {
    const adjustedMs = Math.max(100, Math.floor(ms * DELAY_MULTIPLIER));
    return new Promise(r => setTimeout(r, adjustedMs));
}

// Screenshot directory
const SCREENSHOT_DIR = path.join(__dirname, '..', 'screenshots');

// Ensure screenshot directory exists
if (!fs.existsSync(SCREENSHOT_DIR)) {
    fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}

let screenshotCounter = 0;

/**
 * Take a labeled screenshot and save to screenshots directory
 * @param {object} page - Puppeteer page object
 * @param {string} label - Label for the screenshot filename
 * @returns {string} Path to the saved screenshot
 */
async function takeScreenshot(page, label) {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const filename = `${String(screenshotCounter++).padStart(2, '0')}-${label}-${timestamp}.png`;
    const filepath = path.join(SCREENSHOT_DIR, filename);
    await page.screenshot({ path: filepath, fullPage: false });
    console.log(`📸 Screenshot: ${filepath}`);
    return filepath;
}

/**
 * Reset screenshot counter (useful for new test suites)
 */
function resetScreenshotCounter() {
    screenshotCounter = 0;
}

/**
 * Navigate to a URL with retry on timeout/network errors.
 *
 * The CI environment is occasionally slow enough that a single page.goto
 * exceeds the navigation timeout (e.g. /library/ on a cold app worker).
 * Retrying the navigation a couple of times turns flaky timeouts into
 * passing runs without masking real failures — if every attempt fails,
 * the last error is rethrown.
 *
 * @param {object} page - Puppeteer page object
 * @param {string} url - URL to navigate to
 * @param {object} options - Options forwarded to page.goto, plus:
 *   - retries: number of retries after the first attempt (default 2)
 *   - retryDelayMs: pause between attempts (default 5000)
 *   - waitUntil: forwarded to page.goto (default 'domcontentloaded')
 *   - timeout: per-attempt timeout in ms (default 45000)
 * @returns {Promise<object|null>} Puppeteer HTTPResponse from the successful goto
 */
async function gotoWithRetry(page, url, options = {}) {
    const {
        retries = 2,
        retryDelayMs = 5000,
        ...gotoOptions
    } = options;
    const navOptions = {
        waitUntil: 'domcontentloaded',
        timeout: 45000,
        ...gotoOptions
    };

    let lastError;
    for (let attempt = 1; attempt <= retries + 1; attempt++) {
        try {
            return await page.goto(url, navOptions);
        } catch (err) {
            lastError = err;
            if (attempt > retries) break;
            console.log(`  gotoWithRetry: attempt ${attempt} for ${url} failed (${err.message}), retrying...`);
            await delay(retryDelayMs);
        }
    }
    throw lastError;
}

/**
 * Wait for an element and click it
 * @param {object} page - Puppeteer page object
 * @param {string} selector - CSS selector
 * @param {object} options - Options with timeout (default 5000ms)
 */
async function waitAndClick(page, selector, options = {}) {
    const timeout = options.timeout || 5000;
    console.log(`  Waiting for: ${selector}`);
    await page.waitForSelector(selector, { timeout, visible: true });
    console.log(`  Clicking: ${selector}`);
    await page.click(selector);
}

/**
 * Wait for an element and type into it
 * @param {object} page - Puppeteer page object
 * @param {string} selector - CSS selector
 * @param {string} text - Text to type
 * @param {object} options - Options with timeout and clear flag
 */
async function waitAndType(page, selector, text, options = {}) {
    const timeout = options.timeout || 5000;
    const clear = options.clear !== false;
    console.log(`  Waiting for: ${selector}`);
    await page.waitForSelector(selector, { timeout, visible: true });
    if (clear) {
        await page.$eval(selector, el => { el.value = ''; });
    }
    console.log(`  Typing into: ${selector}`);
    await page.type(selector, text);
}

/**
 * Get the value of an input element
 * @param {object} page - Puppeteer page object
 * @param {string} selector - CSS selector
 * @returns {string} Input value
 */
async function getInputValue(page, selector) {
    return await page.$eval(selector, el => el.value);
}

/**
 * Get the value of a select element
 * @param {object} page - Puppeteer page object
 * @param {string} selector - CSS selector
 * @returns {string} Selected value
 */
async function getSelectValue(page, selector) {
    return await page.$eval(selector, el => el.value);
}

/**
 * Get CSRF token from page meta tag
 * @param {object} page - Puppeteer page object
 * @returns {string|null} CSRF token or null if not found
 */
async function getCSRFToken(page) {
    return await page.$eval('meta[name="csrf-token"]', el => el.content).catch(() => null);
}

/**
 * Login a user
 * @param {object} page - Puppeteer page object
 * @param {string} username - Username
 * @param {string} password - Password
 * @returns {boolean} True if login succeeded
 */
async function loginUser(page, username, password) {
    const currentUrl = page.url();
    console.log(`  loginUser: current URL = ${currentUrl}`);

    // Check if already logged in
    if ((currentUrl.includes('/settings') || currentUrl.includes('/research') || currentUrl === `${BASE_URL}/`)
        && !currentUrl.includes('/login')) {
        console.log('  loginUser: Already logged in');
        return true;
    }

    // Navigate to login page
    if (!currentUrl.includes('/login')) {
        console.log('  loginUser: Navigating to login page');
        await page.goto(`${BASE_URL}/auth/login`, { waitUntil: 'domcontentloaded' });
    }

    // Wait for form
    try {
        await page.waitForSelector('input[name="username"]', { timeout: 5000 });
    } catch {
        // No login form - might already be logged in
        const url = page.url();
        console.log(`  loginUser: No form found, URL = ${url}`);
        return !url.includes('/login');
    }

    // Clear and fill form
    console.log(`  loginUser: Filling form for ${username}`);
    await page.$eval('input[name="username"]', el => { el.value = ''; });
    await page.$eval('input[name="password"]', el => { el.value = ''; });
    await page.type('input[name="username"]', username);
    await page.type('input[name="password"]', password);
    await page.click('button[type="submit"]');

    // Wait for redirect
    try {
        await page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10000 });
    } catch {
        console.log('  loginUser: Navigation timeout (may already be redirected)');
    }

    // Wait for any async redirects
    await delay(1000);

    const afterUrl = page.url();
    console.log(`  loginUser: After submit URL = ${afterUrl}`);

    return !afterUrl.includes('/login');
}

/**
 * Register a new user
 * @param {object} page - Puppeteer page object
 * @param {string} username - Username
 * @param {string} password - Password
 * @returns {boolean} True if registration succeeded
 */
async function registerUser(page, username, password) {
    console.log('  registerUser: Navigating to register page');
    await page.goto(`${BASE_URL}/auth/register`, { waitUntil: 'domcontentloaded' });

    // Wait for form
    try {
        await page.waitForSelector('input[name="username"]', { timeout: 5000 });
    } catch {
        console.log('  registerUser: No registration form found');
        return false;
    }

    // Fill form
    console.log(`  registerUser: Filling form for ${username}`);
    await page.type('input[name="username"]', username);
    await page.type('input[name="password"]', password);

    // Check for confirm password field
    const confirmInput = await page.$('input[name="confirm_password"]');
    if (confirmInput) {
        await page.type('input[name="confirm_password"]', password);
    }

    // Click acknowledge checkbox if present
    const acknowledgeCheckbox = await page.$('#acknowledge');
    if (acknowledgeCheckbox) {
        console.log('  registerUser: Clicking acknowledge checkbox');
        await acknowledgeCheckbox.click();
    }

    // Submit
    console.log('  registerUser: Submitting form');
    await page.click('button[type="submit"]');

    // Wait for redirect
    try {
        await page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 10000 });
    } catch {
        console.log('  registerUser: Navigation timeout');
    }

    const afterUrl = page.url();
    console.log(`  registerUser: After submit URL = ${afterUrl}`);

    // Check for error messages on the page
    const errorMessages = await page.$$eval(
        '.alert, .alert-danger, .error-message',
        els => els.map(el => el.textContent.trim())
    ).catch(() => []);
    if (errorMessages.length > 0) {
        console.log('  registerUser: Server errors:', errorMessages);
    }

    return !afterUrl.includes('/register');
}

/**
 * Ensure user is logged in (login or register as needed)
 * @param {object} page - Puppeteer page object
 * @param {string} username - Username
 * @param {string} password - Password
 * @returns {boolean} True if logged in
 */
async function ensureLoggedIn(page, username, password) {
    console.log('ensureLoggedIn: Starting...');

    // First check if we can access settings (protected page)
    await page.goto(`${BASE_URL}/settings`, { waitUntil: 'domcontentloaded', timeout: 15000 });
    let url = page.url();
    console.log(`ensureLoggedIn: After settings nav, URL = ${url}`);

    // If we're on settings (not login redirect), we're logged in
    if (url.includes('/settings') && !url.includes('/login')) {
        console.log('ensureLoggedIn: Already logged in!');
        return true;
    }

    // Try to login
    console.log('ensureLoggedIn: Not logged in, trying login...');
    let success = await loginUser(page, username, password);

    if (success) {
        console.log('ensureLoggedIn: Login successful');
        return true;
    }

    // Login failed, try to register
    console.log('ensureLoggedIn: Login failed, trying registration...');
    const registered = await registerUser(page, username, password);

    if (!registered) {
        console.log('ensureLoggedIn: Registration failed');
        return false;
    }

    // Registration succeeded, verify login state
    url = page.url();
    console.log(`ensureLoggedIn: After registration, URL = ${url}`);

    if (url.includes('/login')) {
        // Need to login after registration
        success = await loginUser(page, username, password);
        return success;
    }

    return !url.includes('/login');
}

// Lazy + memoized seeded profile, mirroring BROWSER_CONFIG.userDataDir in
// tests/ui_tests/browser_config.js: create the throwaway dir once and reuse
// it for every launch in this process, so re-invoking getLaunchOptions()
// doesn't strand an extra profile dir until exit.
let seededProfileDir = null;
function getSeededProfileDir() {
    if (!seededProfileDir) {
        seededProfileDir = createSeededChromeProfile();
    }
    return seededProfileDir;
}

/**
 * Get Puppeteer launch options
 * @returns {object} Launch options for puppeteer.launch()
 */
function getLaunchOptions() {
    return {
        headless: HEADLESS,
        slowMo: SLOW_MO,
        protocolTimeout: 120000,
        // Seeded profile that disables Chrome's password leak detection.
        // The CI credentials are in Chrome's breach database, so after a
        // successful login an invisible tab-modal "data breach" dialog
        // would otherwise swallow all real CDP keyboard/mouse input for
        // the rest of the tab's life — see
        // tests/shared/chrome_profile.js and issue #4430.
        userDataDir: getSeededProfileDir(),
        args: ['--no-sandbox', '--disable-setuid-sandbox', '--window-size=1400,900']
    };
}

/**
 * Generate unique test run ID
 * @returns {string} Unique ID based on timestamp and random string
 */
function generateTestRunId() {
    return `${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
}

module.exports = {
    BASE_URL,
    HEADLESS,
    SLOW_MO,
    CI_FAST_MODE,
    DELAY_MULTIPLIER,
    SCREENSHOT_DIR,
    delay,
    takeScreenshot,
    resetScreenshotCounter,
    gotoWithRetry,
    waitAndClick,
    waitAndType,
    getInputValue,
    getSelectValue,
    getCSRFToken,
    loginUser,
    registerUser,
    ensureLoggedIn,
    getLaunchOptions,
    generateTestRunId
};
