/**
 * Comprehensive Responsive UI Testing
 * Tests UI responsiveness across different viewport sizes
 */

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

// Viewport configurations
const VIEWPORTS = {
    mobile: { width: 375, height: 667, deviceScaleFactor: 2, isMobile: true, hasTouch: true },
    tablet: { width: 768, height: 1024, deviceScaleFactor: 2, isMobile: true, hasTouch: true },
    desktop: { width: 1280, height: 720, deviceScaleFactor: 1, isMobile: false, hasTouch: false }
};

// Pages to test - expanded to include subpages
const TEST_PAGES = [
    // Main pages
    { name: 'Home', path: '/' },
    { name: 'Settings', path: '/settings/' },
    { name: 'Metrics', path: '/metrics/' },
    { name: 'History', path: '/history/' },
    { name: 'News', path: '/news/' },

    // Metrics subpages (actual routes from metrics_routes.py)
    { name: 'Metrics-ContextOverflow', path: '/metrics/context-overflow' },
    { name: 'Metrics-Costs', path: '/metrics/costs' },
    { name: 'Metrics-StarReviews', path: '/metrics/star-reviews' },
    { name: 'Metrics-Links', path: '/metrics/links' },

    // News subpages
    { name: 'News-Subscriptions', path: '/news/subscriptions' },
    { name: 'News-NewSubscription', path: '/news/subscriptions/new' },

    // Other important pages
    { name: 'Benchmark', path: '/benchmark/' },
    { name: 'BenchmarkResults', path: '/benchmark/results' },

    // Note: Research results pages require a research_id so can't be tested generically
    // e.g. /results/<research_id>, /progress/<research_id>, /details/<research_id>
];

class ResponsiveUITester {
    constructor(viewport = 'desktop') {
        this.viewport = viewport;
        this.baseUrl = process.env.TEST_BASE_URL || 'http://127.0.0.1:5000';
        this.results = {
            passed: 0,
            failed: 0,
            warnings: 0,
            issues: []
        };

        // Create screenshots directory
        this.screenshotsDir = path.join(__dirname, 'screenshots', viewport);
        if (!process.env.CI && !fs.existsSync(this.screenshotsDir)) {
            fs.mkdirSync(this.screenshotsDir, { recursive: true });
        }

        // Visual mode for debugging
        this.visualMode = process.env.VISUAL_MODE === 'true' || process.env.HEADLESS === 'false';
    }

    /**
     * Take a screenshot with a graceful fallback when the page exceeds
     * Puppeteer's fullPage screenshot ceiling (16384px tall on Chromium).
     *
     * The diagnostic screenshots in this test are nice-to-have, not the
     * test target — failing to capture a screenshot on an over-tall page
     * (Settings / Metrics on narrow mobile viewports) used to bubble up to
     * `testPage`'s catch block and mark the whole page as failed. This
     * helper preserves the fullPage attempt where possible and falls back
     * to a viewport-only screenshot on the documented "Page is too large"
     * protocol error so the run continues and the failure surface stays
     * about real responsive issues.
     */
    async safeScreenshot(opts) {
        try {
            await this.page.screenshot(opts);
            return true;
        } catch (error) {
            const msg = error && error.message ? error.message : String(error);
            if (msg.includes('Page is too large') || msg.includes('captureScreenshot')) {
                try {
                    await this.page.screenshot({ ...opts, fullPage: false });
                    console.log(`  ℹ️ Page exceeded screenshot size limit; saved viewport-only fallback (${opts.path})`);
                    return true;
                } catch (fallbackError) {
                    console.log(`  ⚠️ Screenshot fallback also failed: ${fallbackError.message}`);
                    return false;
                }
            }
            console.log(`  ⚠️ Screenshot failed: ${msg}`);
            return false;
        }
    }

    async init() {
        console.log('Launching browser...');
        this.browser = await puppeteer.launch(getPuppeteerLaunchOptions());

        console.log('Creating new page...');
        this.page = await this.browser.newPage();
        await this.page.setViewport(VIEWPORTS[this.viewport]);

        // First, capture login page screenshots before authentication
        await this.captureLoginPageScreenshots();

        // Authenticate with timeout protection
        console.log('Authenticating...');
        const auth = new AuthHelper(this.page, this.baseUrl);

        // Use a reasonable timeout for authentication
        const authTimeout = new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Authentication timeout')), 30000)
        );

        try {
            // For CI, use the pre-created test user from the workflow
            if (process.env.CI) {
                const testUser = {
                    username: 'test_admin',
                    password: 'testpass123'  // pragma: allowlist secret
                };
                console.log(`Using pre-created test user: ${testUser.username}`);
                await Promise.race([
                    auth.ensureAuthenticated(testUser.username, testUser.password),
                    authTimeout
                ]);
            } else {
                await Promise.race([
                    auth.ensureAuthenticated(),
                    authTimeout
                ]);
            }
            console.log('Authentication successful');
        } catch (error) {
            console.error('Authentication failed:', error.message);
            throw error;
        }
    }

    async captureLoginPageScreenshots() {
        console.log(`📸 Capturing login page screenshots for ${this.viewport} viewport...`);

        try {
            // Navigate to login page
            const loginUrl = `${this.baseUrl}/auth/login`;
            await this.page.goto(loginUrl, {
                waitUntil: 'domcontentloaded',
                timeout: 30000
            });

            // Wait for login form to be visible
            await this.page.waitForSelector('input[name="username"]', { timeout: 5000 });

            // Take screenshot of the login page
            if (!process.env.CI) {
                const screenshotPath = path.join(this.screenshotsDir, `login-page-${this.viewport}.png`);
                await this.safeScreenshot({
                    path: screenshotPath,
                    fullPage: true
                });
                console.log(`  ✅ Login page screenshot saved: ${screenshotPath}`);
            }

            // Check for responsive issues on login page
            const hasHorizontalOverflow = await this.page.evaluate(() => {
                return document.body.scrollWidth > window.innerWidth;
            });

            if (hasHorizontalOverflow) {
                console.log(`  ⚠️ Login page has horizontal overflow at ${this.viewport}`);
                if (!process.env.CI) {
                    const overflowScreenshotPath = path.join(this.screenshotsDir, `login-page-overflow-${this.viewport}.png`);
                    await this.safeScreenshot({
                        path: overflowScreenshotPath,
                        fullPage: true
                    });
                    console.log(`  📸 Overflow screenshot saved: ${overflowScreenshotPath}`);
                }
            }

            // Check touch target sizes for mobile
            if (this.viewport === 'mobile') {
                const hasSmallTouchTargets = await this.page.evaluate(() => {
                    const clickables = document.querySelectorAll('button, input, a');
                    for (const el of clickables) {
                        const rect = el.getBoundingClientRect();
                        if (rect.width > 0 && rect.height > 0 && (rect.width < 44 || rect.height < 44)) {
                            return true;
                        }
                    }
                    return false;
                });

                if (hasSmallTouchTargets) {
                    console.log(`  ⚠️ Login page has small touch targets for mobile`);
                    if (!process.env.CI) {
                        // Highlight small touch targets
                        await this.page.evaluate(() => {
                            const clickables = document.querySelectorAll('button, input, a');
                            clickables.forEach(el => {
                                const rect = el.getBoundingClientRect();
                                if (rect.width > 0 && rect.height > 0 && (rect.width < 44 || rect.height < 44)) {
                                    el.style.outline = '2px dashed red';
                                    el.style.outlineOffset = '2px';
                                }
                            });
                        });

                        const touchTargetScreenshotPath = path.join(this.screenshotsDir, `login-page-touch-targets-${this.viewport}.png`);
                        await this.safeScreenshot({
                            path: touchTargetScreenshotPath,
                            fullPage: true
                        });
                        console.log(`  📸 Touch target screenshot saved: ${touchTargetScreenshotPath}`);
                    }
                }
            }

            // Also capture registration page screenshot
            const registerUrl = `${this.baseUrl}/auth/register`;
            await this.page.goto(registerUrl, {
                waitUntil: 'domcontentloaded',
                timeout: 30000
            });

            // Wait for registration form to be visible
            await this.page.waitForSelector('input[name="username"]', { timeout: 5000 });

            if (!process.env.CI) {
                const registerScreenshotPath = path.join(this.screenshotsDir, `register-page-${this.viewport}.png`);
                await this.safeScreenshot({
                    path: registerScreenshotPath,
                    fullPage: true
                });
                console.log(`  ✅ Registration page screenshot saved: ${registerScreenshotPath}`);
            }

            console.log(`✅ Login/register page screenshots captured for ${this.viewport}`);
        } catch (error) {
            console.error(`❌ Error capturing login page screenshots: ${error.message}`);
            // Don't throw - this is not critical for the test to continue
        }
    }

    async testPage(pageInfo) {
        const url = `${this.baseUrl}${pageInfo.path}`;
        console.log(`Testing ${pageInfo.name} at ${this.viewport} viewport...`);

        try {
            const response = await this.page.goto(url, {
                waitUntil: 'domcontentloaded',
                timeout: 30000
            });

            // Check if page exists (skip 404s)
            if (response && response.status() === 404) {
                console.log(`  ⚠️ Skipping ${pageInfo.name} - page not found (404)`);
                return;
            }

            // Check if we were redirected to login page (indicates session lost)
            const currentUrl = this.page.url();
            if (currentUrl.includes('/auth/login') || currentUrl.includes('/auth/register')) {
                console.log(`  ⚠️ Redirected to auth page - re-authenticating...`);
                // Re-authenticate if needed
                const authHelper = new AuthHelper(this.page);
                const authResult = await authHelper.ensureAuthenticated();
                if (authResult.authenticated) {
                    // Try navigating to the page again
                    await this.page.goto(url, {
                        waitUntil: 'domcontentloaded',
                        timeout: 30000
                    });
                }
            }

            // Check for horizontal overflow
            const hasHorizontalOverflow = await this.page.evaluate(() => {
                return document.body.scrollWidth > window.innerWidth;
            });

            if (hasHorizontalOverflow) {
                this.results.warnings++;
                this.results.issues.push(`⚠️ ${pageInfo.name}: Horizontal overflow detected at ${this.viewport}`);

                // Take screenshot if not in CI
                if (!process.env.CI) {
                    const screenshotPath = path.join(this.screenshotsDir, `${pageInfo.name.toLowerCase()}-overflow.png`);
                    await this.safeScreenshot({ path: screenshotPath, fullPage: true });
                    console.log(`  📸 Screenshot saved: ${screenshotPath}`);
                }
            }

            // Check for overlapping elements
            const hasOverlaps = await this.checkForOverlaps();
            if (hasOverlaps) {
                this.results.warnings++;
                this.results.issues.push(`⚠️ ${pageInfo.name}: Overlapping elements detected at ${this.viewport}`);

                // Highlight overlapping elements and take screenshot if not in CI
                if (!process.env.CI) {
                    await this.page.evaluate(() => {
                        const elements = document.querySelectorAll('button, a, input, select');
                        elements.forEach(el => {
                            el.style.outline = '2px solid red';
                        });
                    });
                    const screenshotPath = path.join(this.screenshotsDir, `${pageInfo.name.toLowerCase()}-overlaps.png`);
                    await this.safeScreenshot({ path: screenshotPath, fullPage: true });
                    console.log(`  📸 Screenshot saved: ${screenshotPath}`);
                }
            }

            // Check text readability
            const hasSmallText = await this.page.evaluate(() => {
                const elements = document.querySelectorAll('*');
                for (const el of elements) {
                    const style = window.getComputedStyle(el);
                    const fontSize = parseFloat(style.fontSize);
                    if (fontSize > 0 && fontSize < 12 && el.textContent.trim()) {
                        return true;
                    }
                }
                return false;
            });

            if (hasSmallText && this.viewport === 'mobile') {
                this.results.warnings++;
                this.results.issues.push(`⚠️ ${pageInfo.name}: Text too small for mobile viewing`);

                // Highlight small text and take screenshot if not in CI
                if (!process.env.CI) {
                    await this.page.evaluate(() => {
                        const elements = document.querySelectorAll('*');
                        elements.forEach(el => {
                            const style = window.getComputedStyle(el);
                            const fontSize = parseFloat(style.fontSize);
                            if (fontSize > 0 && fontSize < 12 && el.textContent.trim()) {
                                el.style.backgroundColor = 'yellow';
                                el.style.border = '1px solid orange';
                            }
                        });
                    });
                    const screenshotPath = path.join(this.screenshotsDir, `${pageInfo.name.toLowerCase()}-small-text.png`);
                    await this.safeScreenshot({ path: screenshotPath, fullPage: true });
                    console.log(`  📸 Screenshot saved: ${screenshotPath}`);
                }
            }

            // Check touch target sizes for mobile
            if (this.viewport === 'mobile') {
                const hasSmallTouchTargets = await this.page.evaluate(() => {
                    const clickables = document.querySelectorAll('button, a, input, select, textarea');
                    for (const el of clickables) {
                        const rect = el.getBoundingClientRect();
                        if (rect.width > 0 && rect.height > 0 && (rect.width < 44 || rect.height < 44)) {
                            return true;
                        }
                    }
                    return false;
                });

                if (hasSmallTouchTargets) {
                    this.results.warnings++;
                    this.results.issues.push(`⚠️ ${pageInfo.name}: Touch targets too small (< 44px)`);

                    // Highlight small touch targets and take screenshot if not in CI
                    if (!process.env.CI) {
                        await this.page.evaluate(() => {
                            const clickables = document.querySelectorAll('button, a, input, select, textarea');
                            clickables.forEach(el => {
                                const rect = el.getBoundingClientRect();
                                if (rect.width > 0 && rect.height > 0 && (rect.width < 44 || rect.height < 44)) {
                                    el.style.outline = '2px dashed blue';
                                    el.style.outlineOffset = '2px';
                                }
                            });
                        });
                        const screenshotPath = path.join(this.screenshotsDir, `${pageInfo.name.toLowerCase()}-small-touch-targets.png`);
                        await this.safeScreenshot({ path: screenshotPath, fullPage: true });
                        console.log(`  📸 Screenshot saved: ${screenshotPath}`);
                    }
                }
            }

            this.results.passed++;
            console.log(`✅ ${pageInfo.name} passed basic responsive checks`);

            // Always capture screenshot for BenchmarkResults page
            if (pageInfo.name === 'BenchmarkResults' && !process.env.CI) {
                const screenshotPath = path.join(this.screenshotsDir, `benchmarkresults-page.png`);
                await this.safeScreenshot({ path: screenshotPath, fullPage: true });
                console.log(`  📸 Screenshot saved: ${screenshotPath}`);
            }

        } catch (error) {
            this.results.failed++;
            this.results.issues.push(`❌ ${pageInfo.name}: ${error.message}`);
            console.error(`❌ Error testing ${pageInfo.name}:`, error.message);
        }
    }

    async checkForOverlaps() {
        return await this.page.evaluate(() => {
            const elements = document.querySelectorAll('button, a, input, select');
            const rects = [];

            for (const el of elements) {
                const rect = el.getBoundingClientRect();
                if (rect.width > 0 && rect.height > 0) {
                    rects.push(rect);
                }
            }

            // Check for overlaps
            for (let i = 0; i < rects.length; i++) {
                for (let j = i + 1; j < rects.length; j++) {
                    const r1 = rects[i];
                    const r2 = rects[j];

                    const overlap = !(r1.right < r2.left ||
                                     r1.left > r2.right ||
                                     r1.bottom < r2.top ||
                                     r1.top > r2.bottom);

                    if (overlap) {
                        return true;
                    }
                }
            }

            return false;
        });
    }

    async runTests() {
        await this.init();

        console.log(`\n📱 Running Responsive UI Tests for ${this.viewport.toUpperCase()} viewport\n`);

        for (const pageInfo of TEST_PAGES) {
            await this.testPage(pageInfo);
        }

        await this.cleanup();
        this.printResults();
    }

    async cleanup() {
        if (this.browser) {
            await this.browser.close();
        }
    }

    /**
     * Compare the warnings produced by this run against the checked-in
     * baseline (responsive_baseline.json). Three outcomes:
     *
     *   1. Issues match the baseline exactly → return null (test passes).
     *   2. New issues appeared that aren't in the baseline → REGRESSION.
     *      Caller should fail the test loudly so a PR can't introduce a
     *      new responsive bug without notice.
     *   3. Baseline issues are no longer present → BASELINE STALE.
     *      Caller should fail the test so the contributor who fixed the
     *      bug is forced to remove the entry; this prevents the baseline
     *      from silently masking a future regression.
     *
     * Returning a structured result so the caller can format messaging.
     */
    diffAgainstBaseline() {
        const baselinePath = path.join(__dirname, 'responsive_baseline.json');
        let baseline;
        try {
            baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));
        } catch (err) {
            return {
                error: `Could not read responsive_baseline.json: ${err.message}`,
            };
        }
        const expected = (baseline[this.viewport] || []).slice().sort();
        // Strip the leading "⚠️ " emoji+space the test prepends so the JSON
        // file stays plain ASCII and easier to diff.
        const actual = this.results.issues
            .map((issue) => issue.replace(/^⚠️\s+/u, ''))
            .slice()
            .sort();

        const newRegressions = actual.filter((i) => !expected.includes(i));
        const fixedIssues = expected.filter((i) => !actual.includes(i));
        return { newRegressions, fixedIssues };
    }

    printResults() {
        console.log('\n' + '='.repeat(50));
        console.log('RESPONSIVE UI TEST RESULTS');
        console.log('='.repeat(50));
        console.log(`Viewport: ${this.viewport.toUpperCase()}`);
        console.log(`✅ ${this.results.passed} passed`);
        console.log(`❌ ${this.results.failed} failed`);
        console.log(`⚠️  ${this.results.warnings} warnings`);

        if (this.results.issues.length > 0) {
            console.log('\nIMPROVEMENT SUGGESTIONS:');
            this.results.issues.forEach(issue => {
                console.log(issue);
            });
        }

        console.log('='.repeat(50));

        // Page-level test failures (e.g. nav errors) always fail the run.
        if (this.results.failed > 0) {
            process.exit(1);
        }

        // Compare warnings against the checked-in baseline. New responsive
        // bugs fail the run; old bugs that are now fixed also fail so the
        // baseline must be kept honest.
        const diff = this.diffAgainstBaseline();
        if (diff.error) {
            console.error(`\n❌ Baseline check failed: ${diff.error}`);
            process.exit(1);
        }
        if (diff.newRegressions.length > 0) {
            console.error(
                '\n❌ NEW responsive issues not in responsive_baseline.json ' +
                `(${this.viewport}):`
            );
            diff.newRegressions.forEach((i) => console.error(`   + ${i}`));
            console.error(
                '\nIf this is an intentional change, add the lines above to ' +
                `responsive_baseline.json under "${this.viewport}". ` +
                'Otherwise the PR is introducing a responsive regression.'
            );
            process.exit(1);
        }
        // We *warn* but don't fail when baseline entries don't reproduce in
        // this run. Some warnings (notably touch-target detection on dynamic
        // content) are intermittent — failing on a missing one would be flaky.
        // If a maintainer has actually fixed an issue, they can confirm by
        // running the test a few times and then trim responsive_baseline.json
        // manually.
        if (diff.fixedIssues.length > 0) {
            console.warn(
                `\nℹ️  Some baseline entries did not reproduce this run ` +
                `(${this.viewport}):`
            );
            diff.fixedIssues.forEach((i) => console.warn(`   - ${i}`));
            console.warn(
                `\nIf you've fixed these issues, please re-run the test 3+ ` +
                'times and remove the entries above from responsive_baseline.json. ' +
                "(Not failing the run because some warnings are intermittent.)"
            );
        }

        console.log(
            `\n✅ Responsive issues within baseline (${this.viewport}): ` +
            `${this.results.warnings} warning(s) this run, no regressions.`
        );
    }
}

// Main execution
async function main() {
    const viewport = process.argv[2] || 'desktop';

    if (!VIEWPORTS[viewport]) {
        console.error(`Invalid viewport: ${viewport}. Use: mobile, tablet, or desktop`);
        process.exit(1);
    }

    const tester = new ResponsiveUITester(viewport);
    await tester.runTests();
}

// Run if executed directly
if (require.main === module) {
    main().catch(error => {
        console.error('Test execution failed:', error);
        process.exit(1);
    });
}

module.exports = { ResponsiveUITester };
