/**
 * WCAG 2.1/2.2 Compliance Tests using @axe-core/playwright
 * Automated accessibility testing for the LDR web application
 */

import { test, expect } from '@playwright/test';
import { createAxeBuilder, getCriticalViolations, formatViolations } from './axe-helper.js';

// Test configuration
const BASE_URL = process.env.BASE_URL || 'http://localhost:5000';
const TEST_TIMEOUT = 30000;

// Color-contrast is excluded because the default theme (sepia/solarized-light)
// has text colors that don't meet WCAG AA 4.5:1 contrast ratios.
// This is a systemic theme design issue tracked separately.
const AXE_DISABLE_RULES = ['color-contrast'];

test.use({
    baseURL: BASE_URL,
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
});

test.describe('WCAG Compliance Tests', () => {
    test.describe.configure({ mode: 'parallel' });

    /**
     * Research Page Tests
     */
    test.describe('Research Page', () => {
        test.beforeEach(async ({ page }) => {
            await page.goto('/', { waitUntil: 'domcontentloaded' });
        });

        test('should have no critical WCAG violations on research page', async ({ page }) => {
            const results = await createAxeBuilder(page, { disableRules: AXE_DISABLE_RULES }).analyze();

            const criticalViolations = getCriticalViolations(results.violations);

            if (criticalViolations.length > 0) {
                console.log(formatViolations(criticalViolations));
            }

            expect(criticalViolations).toHaveLength(0);
        });

        test('should have proper heading structure', async ({ page }) => {
            // Check for exactly one h1 in main content
            const h1Count = await page.locator('main h1').count();
            expect(h1Count).toBe(1);

            // Check heading hierarchy doesn't skip levels within main content
            const headings = await page.locator('main :is(h1, h2, h3, h4, h5, h6)').all();
            let prevLevel = 0;

            for (const heading of headings) {
                const tagName = await heading.evaluate(el => el.tagName.toLowerCase());
                const level = parseInt(tagName.charAt(1));

                // Only check for upward level skips (going deeper)
                if (level > prevLevel) {
                    expect(level - prevLevel).toBeLessThanOrEqual(1);
                }
                prevLevel = level;
            }
        });

        test('should have proper page landmarks', async ({ page }) => {
            // Check for main landmark
            const mainCount = await page.locator('main, [role="main"]').count();
            expect(mainCount).toBeGreaterThanOrEqual(1);

            // Check for navigation landmark
            const navCount = await page.locator('nav, [role="navigation"]').count();
            expect(navCount).toBeGreaterThanOrEqual(1);
        });

        test('form inputs should have associated labels', async ({ page }) => {
            const inputs = await page.locator('input:not([type="hidden"]), select, textarea').all();

            for (const input of inputs) {
                const hasLabel = await input.evaluate((el) => {
                    const id = el.id;
                    if (id && document.querySelector(`label[for="${id}"]`)) return true;
                    if (el.closest('label')) return true;
                    if (el.getAttribute('aria-label')) return true;
                    if (el.getAttribute('aria-labelledby')) return true;
                    return false;
                });

                expect(hasLabel, `Input missing label: ${await input.evaluate(el => el.outerHTML.slice(0, 100))}`).toBeTruthy();
            }
        });

        test('images should have alt text', async ({ page }) => {
            const images = await page.locator('img').all();

            for (const img of images) {
                const hasAlt = await img.evaluate((el) => {
                    return el.hasAttribute('alt') || el.hasAttribute('aria-label');
                });

                expect(hasAlt, `Image missing alt: ${await img.getAttribute('src')}`).toBeTruthy();
            }
        });

        test('buttons should have accessible names', async ({ page }) => {
            const buttons = await page.locator('button').all();

            for (const button of buttons) {
                const hasName = await button.evaluate((el) => {
                    return el.getAttribute('aria-label') ||
                           el.getAttribute('aria-labelledby') ||
                           el.textContent?.trim() ||
                           el.getAttribute('title');
                });

                expect(hasName, `Button missing accessible name`).toBeTruthy();
            }
        });

        test('links should have discernible text', async ({ page }) => {
            const links = await page.locator('a[href]').all();

            for (const link of links) {
                const hasText = await link.evaluate((el) => {
                    return el.textContent?.trim() ||
                           el.getAttribute('aria-label') ||
                           el.getAttribute('aria-labelledby') ||
                           el.querySelector('img[alt]') !== null;
                });

                expect(hasText, `Link missing text: ${await link.getAttribute('href')}`).toBeTruthy();
            }
        });
    });

    /**
     * Settings Page Tests
     */
    test.describe('Settings Page', () => {
        test.beforeEach(async ({ page }) => {
            await page.goto('/settings', { waitUntil: 'domcontentloaded' });
        });

        test('should have no critical WCAG violations on settings page', async ({ page }) => {
            const results = await createAxeBuilder(page, { disableRules: AXE_DISABLE_RULES }).analyze();
            const criticalViolations = getCriticalViolations(results.violations);

            if (criticalViolations.length > 0) {
                console.log(formatViolations(criticalViolations));
            }

            expect(criticalViolations).toHaveLength(0);
        });

        test('form sections should have proper labels', async ({ page }) => {
            // Check that form groups have labels or legends
            const formGroups = await page.locator('.form-group, .ldr-form-group').all();

            for (const group of formGroups) {
                const hasLabel = await group.evaluate((el) => {
                    return el.querySelector('label, legend') !== null;
                });

                expect(hasLabel, 'Form group missing label/legend').toBeTruthy();
            }
        });
    });

    /**
     * History Page Tests
     */
    test.describe('History Page', () => {
        test.beforeEach(async ({ page }) => {
            await page.goto('/history', { waitUntil: 'domcontentloaded' });
        });

        test('should have no critical WCAG violations on history page', async ({ page }) => {
            const results = await createAxeBuilder(page, { disableRules: AXE_DISABLE_RULES }).analyze();
            const criticalViolations = getCriticalViolations(results.violations);

            if (criticalViolations.length > 0) {
                console.log(formatViolations(criticalViolations));
            }

            expect(criticalViolations).toHaveLength(0);
        });
    });

    /**
     * Navigation Tests
     */
    test.describe('Navigation', () => {
        test.beforeEach(async ({ page }) => {
            await page.goto('/', { waitUntil: 'domcontentloaded' });
        });

        test('should be keyboard navigable', async ({ page }) => {
            // Tab through the page and verify focus moves logically
            const focusableElements = [];

            // Press Tab multiple times and track focused elements
            for (let i = 0; i < 10; i++) {
                await page.keyboard.press('Tab');
                const focused = await page.evaluate(() => {
                    const el = document.activeElement;
                    return el ? { tag: el.tagName, id: el.id, class: el.className } : null;
                });
                if (focused) {
                    focusableElements.push(focused);
                }
            }

            // Should have moved focus through multiple elements
            expect(focusableElements.length).toBeGreaterThan(1);
        });

        test('focus should be visible', async ({ page }) => {
            // Focus the first interactive element
            await page.keyboard.press('Tab');

            const focusedStyles = await page.evaluate(() => {
                const el = document.activeElement;
                if (!el) return null;
                const styles = window.getComputedStyle(el);
                return {
                    outline: styles.outline,
                    outlineWidth: styles.outlineWidth,
                    boxShadow: styles.boxShadow
                };
            });

            // Should have some visible focus indicator
            const hasVisibleFocus = focusedStyles && (
                focusedStyles.outline !== 'none' ||
                focusedStyles.outlineWidth !== '0px' ||
                focusedStyles.boxShadow !== 'none'
            );

            expect(hasVisibleFocus).toBeTruthy();
        });
    });

    /**
     * Dynamic Content Tests
     */
    test.describe('Dynamic Content', () => {
        test('alerts should have proper ARIA attributes', async ({ page }) => {
            await page.goto('/', { waitUntil: 'domcontentloaded' });

            // Check that any alert containers have proper attributes
            const alertContainers = await page.locator('[role="alert"], .alert-container, .ldr-settings-alert-container').all();

            for (const container of alertContainers) {
                const hasLiveRegion = await container.evaluate((el) => {
                    const role = el.getAttribute('role');
                    return (role === 'alert' || role === 'status') ||
                           el.hasAttribute('aria-live') ||
                           el.closest('[aria-live]');
                });

                expect(hasLiveRegion, 'Alert container missing role="alert", role="status", or aria-live attribute').toBeTruthy();
            }
        });

        test('progress bars should have proper ARIA attributes', async ({ page }) => {
            await page.goto('/', { waitUntil: 'domcontentloaded' });

            const progressBars = await page.locator('[role="progressbar"], .progress-bar, .ldr-progress-bar').all();
            test.skip(progressBars.length === 0, 'No progress bars found on this page');

            for (const bar of progressBars) {
                const hasAria = await bar.evaluate((el) => {
                    // Check if element or parent has progressbar role
                    const progressEl = el.closest('[role="progressbar"]') || el;
                    if (progressEl.getAttribute('role') !== 'progressbar') return false; // Missing progressbar role

                    return progressEl.hasAttribute('aria-valuenow') &&
                           progressEl.hasAttribute('aria-valuemin') &&
                           progressEl.hasAttribute('aria-valuemax');
                });

                expect(hasAria, 'Progress bar missing role="progressbar" or required aria-valuenow/min/max attributes').toBeTruthy();
            }
        });
    });
});

/**
 * Component-Specific Tests
 */
test.describe('Component Accessibility', () => {
    test.describe('Custom Dropdown', () => {
        test.beforeEach(async ({ page }) => {
            await page.goto('/', { waitUntil: 'domcontentloaded' });
            // Open advanced options to see dropdowns
            const toggleExists = await page.locator('#advanced-options-toggle').count();
            if (toggleExists > 0) {
                await page.click('#advanced-options-toggle');
            }
        });

        test('dropdowns should have proper combobox structure', async ({ page }) => {
            const comboboxes = await page.locator('[role="combobox"]').all();

            for (const combobox of comboboxes) {
                // Check aria-controls points to listbox
                const controls = await combobox.getAttribute('aria-controls');
                expect(controls, 'Combobox missing aria-controls').toBeTruthy();

                // Verify the controlled element exists and has listbox role
                if (controls) {
                    const listbox = await page.locator(`#${controls}`);
                    const count = await listbox.count();
                    if (count > 0) {
                        const role = await listbox.getAttribute('role');
                        expect(role).toBe('listbox');
                    }
                }
            }
        });
    });

    test.describe('Mode Selection Radio Group', () => {
        test.beforeEach(async ({ page }) => {
            await page.goto('/', { waitUntil: 'domcontentloaded' });
        });

        test('radio group should have proper structure', async ({ page }) => {
            const fieldset = page.locator('fieldset');
            await expect(fieldset).toBeVisible();
            const legend = page.locator('fieldset legend');
            await expect(legend).toBeVisible();
            const radios = page.locator('input[type="radio"]');
            const radioCount = await radios.count();
            expect(radioCount).toBeGreaterThanOrEqual(2);
            for (let i = 0; i < radioCount; i++) {
                const radio = radios.nth(i);
                await expect(radio).toHaveAttribute('name', 'research_mode');
                const id = await radio.getAttribute('id');
                expect(id).toBeTruthy();
                const label = page.locator(`label[for="${id}"]`);
                await expect(label).toBeVisible();
            }
        });

        test('radio options should be keyboard accessible', async ({ page }) => {
            const firstLabel = page.locator('.ldr-mode-option').first();
            await firstLabel.focus();
            await page.keyboard.press('ArrowRight');
            const secondLabel = page.locator('.ldr-mode-option').nth(1);
            await expect(secondLabel).toBeFocused();
        });
    });
});

/**
 * Full Page Scans - Run Less Frequently
 */
test.describe('Full Page Accessibility Scan', () => {
    test.describe.configure({ mode: 'serial' });

    const pages = [
        { name: 'Research', path: '/' },
        { name: 'History', path: '/history' },
        { name: 'Settings', path: '/settings' },
    ];

    for (const pageInfo of pages) {
        test(`${pageInfo.name} page should pass WCAG 2.1 AA`, async ({ page }) => {
            await page.goto(pageInfo.path, { waitUntil: 'domcontentloaded' });

            const results = await createAxeBuilder(page, { disableRules: AXE_DISABLE_RULES }).analyze();

            const criticalViolations = getCriticalViolations(results.violations);

            if (criticalViolations.length > 0) {
                console.log(`\n${pageInfo.name} page violations:`);
                console.log(formatViolations(criticalViolations));
            }

            // Attach results to test output
            test.info().annotations.push({
                type: 'accessibility-summary',
                description: `${pageInfo.name}: ${results.violations.length} violations, ${results.passes.length} passes`
            });

            expect(criticalViolations).toHaveLength(0);
        });
    }
});
