/**
 * Layout Bug Tests for CI
 *
 * Comprehensive automated tests that catch mobile and desktop UI layout bugs.
 * Uses geometry-based assertions that are reliable in headless CI environments.
 *
 * Run: node test_layout_bugs_ci.js
 *
 * Environment variables:
 *   - BASE_URL: Server URL (default: http://localhost:5000)
 *   - CI: Set to 'true' for CI mode (no screenshots on pass)
 *   - OUTPUT_FORMAT: 'json' or 'junit' (default: json)
 *   - HEADLESS: 'true' or 'false' (default: true)
 */

const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const AuthHelper = require('../auth_helper');

// Configuration
const BASE_URL = process.env.BASE_URL || 'http://127.0.0.1:5000';
const IS_CI = process.env.CI === 'true';
const OUTPUT_FORMAT = process.env.OUTPUT_FORMAT || 'json';
const HEADLESS = process.env.HEADLESS !== 'false';

// Viewports to test - covers mobile, tablet, desktop, and short heights
// Note: The app uses 768px as mobile breakpoint, so 767px is mobile, 768px is tablet
const VIEWPORTS = {
    'mobile_iPhone_SE': { width: 375, height: 667, isMobile: true },
    'mobile_iPhone_14': { width: 430, height: 932, isMobile: true },
    'tablet_iPad': { width: 768, height: 1024, isMobile: false },  // 768px = tablet (sidebar visible)
    'desktop_normal': { width: 1280, height: 800, isMobile: false },
    'desktop_short': { width: 1280, height: 600, isMobile: false },
    'desktop_very_short': { width: 1280, height: 500, isMobile: false },
    'desktop_wide': { width: 1920, height: 1080, isMobile: false },
};

// Pages to test (authenticated)
const AUTHENTICATED_PAGES = [
    { path: '/', name: 'Research' },
    { path: '/history/', name: 'History' },
    { path: '/news/', name: 'News' },
    { path: '/settings/', name: 'Settings' },
    { path: '/metrics/', name: 'Metrics' },
];

// Pages to test (unauthenticated)
const UNAUTHENTICATED_PAGES = [
    { path: '/auth/login', name: 'Login' },
];

// Test results collector
class TestResults {
    constructor() {
        this.tests = [];
        this.startTime = Date.now();
    }

    addResult(viewport, page, testName, passed, message = '', duration = 0) {
        this.tests.push({
            viewport,
            page,
            testName,
            passed,
            message,
            duration,
            timestamp: new Date().toISOString()
        });
    }

    get summary() {
        const total = this.tests.length;
        const passed = this.tests.filter(t => t.passed).length;
        const failed = total - passed;
        return { total, passed, failed, duration: Date.now() - this.startTime };
    }

    toJSON() {
        return {
            summary: this.summary,
            tests: this.tests
        };
    }

    toJUnitXML() {
        const { summary } = this;
        const testsByViewport = {};

        for (const test of this.tests) {
            if (!testsByViewport[test.viewport]) {
                testsByViewport[test.viewport] = [];
            }
            testsByViewport[test.viewport].push(test);
        }

        let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`;
        xml += `<testsuites name="Layout Bug Tests" tests="${summary.total}" failures="${summary.failed}" time="${(summary.duration / 1000).toFixed(2)}">\n`;

        for (const [viewport, tests] of Object.entries(testsByViewport)) {
            const vpPassed = tests.filter(t => t.passed).length;
            const vpFailed = tests.length - vpPassed;
            const vpTime = tests.reduce((sum, t) => sum + t.duration, 0) / 1000;

            xml += `  <testsuite name="${viewport}" tests="${tests.length}" failures="${vpFailed}" time="${vpTime.toFixed(2)}">\n`;

            for (const test of tests) {
                xml += `    <testcase name="${test.page} - ${test.testName}" time="${(test.duration / 1000).toFixed(3)}"`;
                if (test.passed) {
                    xml += ` />\n`;
                } else {
                    xml += `>\n`;
                    xml += `      <failure message="${escapeXml(test.message)}">${escapeXml(test.message)}</failure>\n`;
                    xml += `    </testcase>\n`;
                }
            }

            xml += `  </testsuite>\n`;
        }

        xml += `</testsuites>\n`;
        return xml;
    }
}

function escapeXml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&apos;');
}

// Layout assertions - all geometry-based, no animation dependencies
const Assertions = {
    /**
     * Check for horizontal overflow (content wider than viewport)
     */
    async noHorizontalOverflow(page) {
        const result = await page.evaluate(() => {
            const scrollWidth = document.documentElement.scrollWidth;
            const innerWidth = window.innerWidth;
            const overflow = scrollWidth - innerWidth;
            return {
                passed: overflow <= 5, // 5px tolerance for scrollbars
                scrollWidth,
                innerWidth,
                overflow
            };
        });
        return {
            passed: result.passed,
            message: result.passed
                ? 'No horizontal overflow'
                : `Horizontal overflow: ${result.overflow}px (scrollWidth=${result.scrollWidth}, innerWidth=${result.innerWidth})`
        };
    },

    /**
     * Check sidebar visibility matches expected state for viewport
     * - Mobile (<768px): sidebar hidden
     * - Tablet (768-991px): sidebar collapsed (60px)
     * - Desktop (≥992px): sidebar full width (200-240px)
     */
    async sidebarCorrectState(page, viewportName, viewportConfig) {
        const isMobileWidth = viewportConfig.width < 768;
        const isTabletWidth = viewportConfig.width >= 768 && viewportConfig.width <= 991;

        // On mobile, sidebar should be hidden
        // On tablet/desktop, sidebar should be visible (but may be collapsed on tablet)
        const shouldBeVisible = !isMobileWidth;

        const result = await page.evaluate(() => {
            const sidebar = document.querySelector('.ldr-sidebar');
            if (!sidebar) {
                return { exists: false, isVisible: false };
            }

            const style = window.getComputedStyle(sidebar);
            const rect = sidebar.getBoundingClientRect();

            // Check various ways sidebar could be hidden
            const displayNone = style.display === 'none';
            const visibilityHidden = style.visibility === 'hidden';
            const offScreen = rect.right <= 0;
            const zeroWidth = rect.width === 0;
            const transformHidden = style.transform && style.transform.includes('-100%');

            const isHidden = displayNone || visibilityHidden || offScreen || zeroWidth || transformHidden;

            return {
                exists: true,
                isVisible: !isHidden,
                display: style.display,
                visibility: style.visibility,
                width: rect.width,
                left: rect.left,
                right: rect.right
            };
        });

        if (!result.exists) {
            // Sidebar might not exist on some pages (like login)
            return { passed: true, message: 'Sidebar not present on page (expected for some pages)' };
        }

        // For tablet, sidebar should exist but may be collapsed (60px)
        if (isTabletWidth) {
            const isCollapsedOrVisible = result.width >= 50 || result.isVisible;
            return {
                passed: isCollapsedOrVisible,
                message: isCollapsedOrVisible
                    ? `Sidebar correctly ${result.width >= 100 ? 'visible' : 'collapsed'} on tablet (width=${result.width}px)`
                    : `Sidebar should be visible/collapsed on tablet but is hidden (width=${result.width}px)`
            };
        }

        const stateCorrect = result.isVisible === shouldBeVisible;
        const expectedState = shouldBeVisible ? 'visible' : 'hidden';
        const actualState = result.isVisible ? 'visible' : 'hidden';

        return {
            passed: stateCorrect,
            message: stateCorrect
                ? `Sidebar correctly ${expectedState}`
                : `Sidebar should be ${expectedState} but is ${actualState} (width=${result.width}, left=${result.left})`
        };
    },

    /**
     * Check mobile nav visibility matches expected state
     * @param {Page} page - Puppeteer page
     * @param {string} viewportName - Name of viewport
     * @param {object} viewportConfig - Viewport configuration
     * @param {boolean} isAuthenticatedPage - Whether this is an authenticated page
     */
    async mobileNavCorrectState(page, viewportName, viewportConfig, isAuthenticatedPage = true) {
        const isMobileWidth = viewportConfig.width < 768;

        // Mobile nav should only be visible on mobile widths AND authenticated pages
        // Unauthenticated pages (login, register) don't have mobile nav
        const shouldBeVisible = isMobileWidth && isAuthenticatedPage;

        const result = await page.evaluate(() => {
            const mobileNav = document.querySelector('.ldr-mobile-bottom-nav');
            if (!mobileNav) {
                return { exists: false, isVisible: false };
            }

            const style = window.getComputedStyle(mobileNav);
            const rect = mobileNav.getBoundingClientRect();

            const displayNone = style.display === 'none';
            const visibilityHidden = style.visibility === 'hidden';
            const offScreen = rect.top >= window.innerHeight;
            const zeroHeight = rect.height === 0;

            const isHidden = displayNone || visibilityHidden || offScreen || zeroHeight;

            return {
                exists: true,
                isVisible: !isHidden,
                display: style.display,
                visibility: style.visibility,
                height: rect.height,
                top: rect.top,
                bottom: rect.bottom
            };
        });

        if (!result.exists) {
            if (shouldBeVisible) {
                return { passed: false, message: 'Mobile nav should exist but is not found in DOM' };
            }
            // Not visible and not expected - that's correct
            const reason = !isAuthenticatedPage ? 'unauthenticated page' : 'desktop viewport';
            return { passed: true, message: `Mobile nav not present (expected for ${reason})` };
        }

        const stateCorrect = result.isVisible === shouldBeVisible;
        const expectedState = shouldBeVisible ? 'visible' : 'hidden';
        const actualState = result.isVisible ? 'visible' : 'hidden';

        return {
            passed: stateCorrect,
            message: stateCorrect
                ? `Mobile nav correctly ${expectedState}`
                : `Mobile nav should be ${expectedState} but is ${actualState} (height=${result.height}, top=${result.top})`
        };
    },

    /**
     * Check that all navigation items are accessible (Settings not cut off)
     * @param {Page} page - Puppeteer page
     * @param {object} viewportConfig - Viewport configuration
     * @param {boolean} isAuthenticatedPage - Whether this is an authenticated page
     */
    async allNavItemsAccessible(page, viewportConfig, isAuthenticatedPage = true) {
        const isMobileWidth = viewportConfig.width < 768;

        // Unauthenticated pages don't have navigation menus
        if (!isAuthenticatedPage) {
            return { passed: true, message: 'Navigation check skipped for unauthenticated page' };
        }

        if (isMobileWidth) {
            // For mobile, check that Settings items exist in the sheet menu
            // Note: Sheet menu uses <button> elements with data-action attributes, not <a> links
            const result = await page.evaluate(() => {
                const sheetMenu = document.querySelector('.ldr-mobile-sheet-menu');
                if (!sheetMenu) {
                    return { exists: false, settingsFound: false };
                }

                // Look for Settings/Configuration buttons in sheet menu by data-action
                const allButtons = Array.from(sheetMenu.querySelectorAll('button[data-action], .ldr-mobile-sheet-item'));
                const settingsItems = allButtons.filter(btn => {
                    const action = btn.getAttribute('data-action') || '';
                    const text = btn.textContent.toLowerCase();
                    return action.includes('/settings') ||
                           action.includes('/embedding') ||
                           text.includes('settings') ||
                           text.includes('embeddings') ||
                           text.includes('configuration');
                });

                // Also check for Settings section title
                const sectionTitles = Array.from(sheetMenu.querySelectorAll('.ldr-mobile-sheet-title, [class*="sheet-title"]'));
                const settingsSectionTitle = sectionTitles.find(el =>
                    el.textContent.toLowerCase().includes('settings')
                );

                return {
                    exists: true,
                    settingsFound: settingsItems.length > 0 || !!settingsSectionTitle,
                    settingsItemsCount: settingsItems.length,
                    totalItems: allButtons.length,
                    sheetHeight: sheetMenu.getBoundingClientRect().height,
                    maxHeight: window.getComputedStyle(sheetMenu).maxHeight
                };
            });

            if (!result.exists) {
                return { passed: true, message: 'Sheet menu not on page (may not be needed)' };
            }

            return {
                passed: result.settingsFound,
                message: result.settingsFound
                    ? `Settings accessible in mobile sheet menu (${result.settingsItemsCount} items found)`
                    : `Settings items NOT found in mobile sheet menu (${result.totalItems} total items, height=${result.sheetHeight}px)`
            };
        }
        // For desktop/tablet, check sidebar can show all items
        const result = await page.evaluate(() => {
            const sidebar = document.querySelector('.ldr-sidebar');
            if (!sidebar) {
                return { exists: false };
            }

            const nav = sidebar.querySelector('.ldr-sidebar-nav');
            if (!nav) {
                return { exists: true, navExists: false };
            }

            // Get all nav items
            const items = Array.from(nav.querySelectorAll('li a'));

            // Check if Settings section exists
            const settingsSection = Array.from(sidebar.querySelectorAll('.ldr-sidebar-section-label'))
                .find(el => el.textContent.toLowerCase().includes('settings'));

            // Check if any Settings items exist
            const settingsItems = items.filter(a =>
                a.href.includes('settings') ||
                a.href.includes('embeddings') ||
                a.href.includes('configuration')
            );

            // Check if last item is visible (within viewport)
            const sidebarRect = sidebar.getBoundingClientRect();
            const viewportHeight = window.innerHeight;

            // Find the bottom-most item
            let lastItemBottom = 0;
            items.forEach(item => {
                const rect = item.getBoundingClientRect();
                if (rect.bottom > lastItemBottom) {
                    lastItemBottom = rect.bottom;
                }
            });

            const isScrollable = nav.scrollHeight > nav.clientHeight;
            const allItemsInView = lastItemBottom <= viewportHeight;

            return {
                exists: true,
                navExists: true,
                totalItems: items.length,
                settingsSectionFound: !!settingsSection,
                settingsItemsCount: settingsItems.length,
                sidebarBottom: sidebarRect.bottom,
                lastItemBottom,
                viewportHeight,
                isScrollable,
                allItemsInView: allItemsInView || isScrollable // Either all visible or can scroll to them
            };
        });

        if (!result.exists) {
            return { passed: true, message: 'Sidebar not on page' };
        }

        if (!result.navExists) {
            return { passed: false, message: 'Sidebar exists but nav not found' };
        }

        // We need either Settings items visible OR the section to be scrollable
        const hasSettingsAccess = result.settingsItemsCount > 0 || result.settingsSectionFound;
        const canAccessAll = result.allItemsInView;

        return {
            passed: hasSettingsAccess && canAccessAll,
            message: hasSettingsAccess && canAccessAll
                ? `All ${result.totalItems} nav items accessible (Settings section found)`
                : `Nav items may be cut off: lastItemBottom=${result.lastItemBottom}px, viewportHeight=${result.viewportHeight}px, settingsFound=${hasSettingsAccess}`
        };

    },

    /**
     * Check main content is not overlapped by navigation elements
     * @param {Page} page - Puppeteer page
     * @param {object} viewportConfig - Viewport configuration
     * @param {boolean} isAuthenticatedPage - Whether this is an authenticated page
     */
    async noContentBehindNav(page, viewportConfig, isAuthenticatedPage = true) {
        // Unauthenticated pages don't have the navigation overlay issue
        if (!isAuthenticatedPage) {
            return { passed: true, message: 'Content overlap check skipped for unauthenticated page' };
        }

        const isMobileWidth = viewportConfig.width < 768;

        const result = await page.evaluate((isMobile) => {
            // For mobile, check if the content has proper padding-bottom to account for fixed nav
            // For desktop, check if content properly avoids sidebar
            const mainContent = document.querySelector('main, .main-content, [role="main"], .container');
            if (!mainContent) {
                return { mainExists: false };
            }

            const mainStyle = window.getComputedStyle(mainContent);
            const viewportHeight = window.innerHeight;

            if (isMobile) {
                const mobileNav = document.querySelector('.ldr-mobile-bottom-nav');
                if (!mobileNav) {
                    return { mainExists: true, navExists: false };
                }
                const navRect = mobileNav.getBoundingClientRect();
                const navStyle = window.getComputedStyle(mobileNav);

                if (navStyle.display === 'none' || navRect.height === 0) {
                    return { mainExists: true, navExists: false };
                }

                // The nav is fixed at bottom, check if main content has padding to avoid it
                // Content naturally scrolls behind fixed elements, so we check padding instead
                const paddingBottom = parseFloat(mainStyle.paddingBottom) || 0;
                const marginBottom = parseFloat(mainStyle.marginBottom) || 0;
                const navHeight = navRect.height;

                // Content should have at least ~50px padding/margin to clear the nav
                // This ensures the last content isn't hidden behind the nav
                const hasSufficientSpacing = (paddingBottom + marginBottom) >= (navHeight - 10);

                return {
                    mainExists: true,
                    navExists: true,
                    navHeight,
                    paddingBottom,
                    marginBottom,
                    totalSpacing: paddingBottom + marginBottom,
                    passed: hasSufficientSpacing
                };
            }
            const sidebar = document.querySelector('.ldr-sidebar');
            if (!sidebar) {
                return { mainExists: true, navExists: false };
            }
            const sidebarRect = sidebar.getBoundingClientRect();
            const sidebarStyle = window.getComputedStyle(sidebar);
            const mainRect = mainContent.getBoundingClientRect();

            if (sidebarStyle.display === 'none' || sidebarRect.width === 0) {
                return { mainExists: true, navExists: false };
            }

            // Check horizontal overlap
            const overlap = mainRect.left < sidebarRect.right ? sidebarRect.right - mainRect.left : 0;

            return {
                mainExists: true,
                navExists: true,
                mainLeft: mainRect.left,
                sidebarRight: sidebarRect.right,
                overlap,
                passed: overlap <= 10
            };

        }, isMobileWidth);

        if (!result.mainExists) {
            return { passed: true, message: 'Main content area not found (may be expected)' };
        }

        if (!result.navExists) {
            return { passed: true, message: 'No navigation overlap (nav not present)' };
        }

        if (isMobileWidth) {
            return {
                passed: result.passed,
                message: result.passed
                    ? `Content has proper spacing for mobile nav (spacing=${result.totalSpacing}px, navHeight=${result.navHeight}px)`
                    : `Content needs more bottom padding for mobile nav (spacing=${result.totalSpacing}px < navHeight=${result.navHeight}px)`
            };
        }

        return {
            passed: result.passed,
            message: result.passed
                ? 'No content overlap with sidebar'
                : `Content overlaps sidebar by ${result.overlap}px`
        };
    },

    /**
     * Check touch targets are adequate size on mobile (warning only)
     */
    async touchTargetsAdequate(page, viewportConfig) {
        if (!viewportConfig.isMobile) {
            return { passed: true, message: 'Touch target check skipped for non-mobile viewport' };
        }

        const result = await page.evaluate(() => {
            const interactiveElements = document.querySelectorAll('button, a, input, [role="button"], [onclick]');
            const smallTargets = [];
            const MIN_SIZE = 40; // 40px minimum for touch targets

            interactiveElements.forEach((el) => {
                const rect = el.getBoundingClientRect();
                const style = window.getComputedStyle(el);

                // Skip hidden elements
                if (style.display === 'none' || style.visibility === 'hidden') {
                    return;
                }

                // Skip elements with no size
                if (rect.width === 0 || rect.height === 0) {
                    return;
                }

                if (rect.width < MIN_SIZE || rect.height < MIN_SIZE) {
                    smallTargets.push({
                        tag: el.tagName.toLowerCase(),
                        text: el.textContent?.slice(0, 30) || '',
                        width: Math.round(rect.width),
                        height: Math.round(rect.height),
                        classes: el.className?.split(' ').slice(0, 3).join(' ') || ''
                    });
                }
            });

            return {
                total: interactiveElements.length,
                smallCount: smallTargets.length,
                smallTargets: smallTargets.slice(0, 5) // Limit to first 5
            };
        });

        // This is a warning, not a failure
        const passed = result.smallCount === 0;
        const message = passed
            ? `All ${result.total} touch targets are adequate size (≥40px)`
            : `Warning: ${result.smallCount} touch targets under 40px: ${result.smallTargets.map(t => `${t.tag}(${t.width}x${t.height})`).join(', ')}`;

        return { passed, message, isWarning: !passed };
    }
};

// Main test runner
class LayoutBugTests {
    constructor() {
        this.results = new TestResults();
        this.browser = null;
        this.page = null;
        this.authHelper = null;
    }

    async setup() {
        this.browser = await puppeteer.launch({
            headless: HEADLESS ? 'new' : false,
            args: ['--no-sandbox', '--disable-setuid-sandbox']
        });
        this.page = await this.browser.newPage();

        // Set a large viewport for authentication
        await this.page.setViewport({ width: 1280, height: 800 });

        // Use AuthHelper for authentication
        this.authHelper = new AuthHelper(this.page, BASE_URL);
        await this.authHelper.ensureAuthenticated();

        console.log('Authentication complete');
    }

    async runAllTests() {
        console.log('\n========================================');
        console.log('  Layout Bug Tests for CI');
        console.log('========================================\n');
        console.log(`Base URL: ${BASE_URL}`);
        console.log(`Viewports: ${Object.keys(VIEWPORTS).length}`);
        console.log(`Pages: ${AUTHENTICATED_PAGES.length + UNAUTHENTICATED_PAGES.length}`);
        console.log('');

        // Test authenticated pages
        for (const [viewportName, viewportConfig] of Object.entries(VIEWPORTS)) {
            console.log(`\n--- Testing viewport: ${viewportName} (${viewportConfig.width}x${viewportConfig.height}) ---`);

            await this.page.setViewport({
                width: viewportConfig.width,
                height: viewportConfig.height,
                isMobile: viewportConfig.isMobile,
                hasTouch: viewportConfig.isMobile
            });

            for (const pageInfo of AUTHENTICATED_PAGES) {
                await this.testPage(viewportName, viewportConfig, pageInfo);
            }
        }

        // Test unauthenticated pages (login page)
        // Create new incognito context for unauthenticated tests
        const incognitoContext = await this.browser.createBrowserContext();
        const incognitoPage = await incognitoContext.newPage();

        for (const [viewportName, viewportConfig] of Object.entries(VIEWPORTS)) {
            await incognitoPage.setViewport({
                width: viewportConfig.width,
                height: viewportConfig.height,
                isMobile: viewportConfig.isMobile,
                hasTouch: viewportConfig.isMobile
            });

            for (const pageInfo of UNAUTHENTICATED_PAGES) {
                // Pass false for isAuthenticatedPage
                await this.testPageWithPage(incognitoPage, viewportName, viewportConfig, pageInfo, false);
            }
        }

        await incognitoContext.close();

        return this.results;
    }

    async testPage(viewportName, viewportConfig, pageInfo, isAuthenticatedPage = true) {
        return this.testPageWithPage(this.page, viewportName, viewportConfig, pageInfo, isAuthenticatedPage);
    }

    async testPageWithPage(page, viewportName, viewportConfig, pageInfo, isAuthenticatedPage = true) {
        const url = `${BASE_URL}${pageInfo.path}`;

        try {
            await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
            // Small delay to ensure CSS is fully applied
            await new Promise(resolve => setTimeout(resolve, 500));
        } catch (error) {
            this.results.addResult(viewportName, pageInfo.name, 'Page Load', false, `Failed to load: ${error.message}`);
            return;
        }

        // Run all assertions - pass isAuthenticatedPage to relevant checks
        const assertions = [
            { name: 'No Horizontal Overflow', fn: () => Assertions.noHorizontalOverflow(page) },
            { name: 'Sidebar State', fn: () => Assertions.sidebarCorrectState(page, viewportName, viewportConfig) },
            { name: 'Mobile Nav State', fn: () => Assertions.mobileNavCorrectState(page, viewportName, viewportConfig, isAuthenticatedPage) },
            { name: 'All Nav Items Accessible', fn: () => Assertions.allNavItemsAccessible(page, viewportConfig, isAuthenticatedPage) },
            { name: 'No Content Behind Nav', fn: () => Assertions.noContentBehindNav(page, viewportConfig, isAuthenticatedPage) },
            { name: 'Touch Targets', fn: () => Assertions.touchTargetsAdequate(page, viewportConfig) },
        ];

        for (const assertion of assertions) {
            const startTime = Date.now();
            try {
                const result = await assertion.fn();
                const duration = Date.now() - startTime;

                const icon = result.passed ? '✓' : (result.isWarning ? '⚠' : '✗');
                console.log(`  ${icon} ${pageInfo.name} - ${assertion.name}: ${result.message}`);

                // Treat warnings as passes for CI
                this.results.addResult(
                    viewportName,
                    pageInfo.name,
                    assertion.name,
                    result.passed || result.isWarning,
                    result.message,
                    duration
                );
            } catch (error) {
                const duration = Date.now() - startTime;
                console.log(`  ✗ ${pageInfo.name} - ${assertion.name}: Error - ${error.message}`);
                this.results.addResult(viewportName, pageInfo.name, assertion.name, false, `Error: ${error.message}`, duration);
            }
        }
    }

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

// Output helpers
function writeResults(results) {
    const outputDir = path.join(__dirname, 'test-results');
    if (!fs.existsSync(outputDir)) {
        fs.mkdirSync(outputDir, { recursive: true });
    }

    // Always write JSON
    const jsonPath = path.join(outputDir, 'layout-tests.json');
    fs.writeFileSync(jsonPath, JSON.stringify(results.toJSON(), null, 2));
    console.log(`\nJSON results: ${jsonPath}`);

    // Write JUnit XML if requested
    if (OUTPUT_FORMAT === 'junit' || IS_CI) {
        const xmlPath = path.join(outputDir, 'layout-tests.xml');
        fs.writeFileSync(xmlPath, results.toJUnitXML());
        console.log(`JUnit XML: ${xmlPath}`);
    }
}

function printSummary(results) {
    const { summary } = results;

    console.log('\n========================================');
    console.log('  Test Summary');
    console.log('========================================');
    console.log(`  Total:  ${summary.total}`);
    console.log(`  Passed: ${summary.passed} (${((summary.passed / summary.total) * 100).toFixed(1)}%)`);
    console.log(`  Failed: ${summary.failed}`);
    console.log(`  Time:   ${(summary.duration / 1000).toFixed(2)}s`);
    console.log('========================================\n');

    if (summary.failed > 0) {
        console.log('Failed Tests:');
        for (const test of results.tests.filter(t => !t.passed)) {
            console.log(`  ✗ [${test.viewport}] ${test.page} - ${test.testName}`);
            console.log(`    ${test.message}`);
        }
        console.log('');
    }
}

// Main execution
async function main() {
    const tester = new LayoutBugTests();

    try {
        await tester.setup();
        const results = await tester.runAllTests();

        printSummary(results);
        writeResults(results);

        // Exit with error code if tests failed
        const exitCode = results.summary.failed > 0 ? 1 : 0;
        process.exit(exitCode);
    } catch (error) {
        console.error('Fatal error:', error);
        process.exit(1);
    } finally {
        await tester.cleanup();
    }
}

main();
