import AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';

const email = process.env.E2E_EMAIL;
const password = process.env.E2E_PASSWORD;

test('Inertia foundation navigates and exposes Laravel validation without a full reload', async ({ page }) => {
    await page.goto('/__kview/inertia');

    await expect(page.getByRole('heading', { name: 'Vue fonctionne à côté de Blade, sans rupture.' })).toBeVisible();
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');

    const documentMarker = await page.evaluate(() => {
        (window as typeof window & { __inertiaDocumentMarker?: string }).__inertiaDocumentMarker = crypto.randomUUID();
        return (window as typeof window & { __inertiaDocumentMarker: string }).__inertiaDocumentMarker;
    });

    await page.getByRole('link', { name: 'Tester la navigation partielle' }).click();
    await expect(page.getByRole('heading', { name: 'Laravel reste le routeur. Inertia évite le rechargement complet.' })).toBeVisible();
    await expect.poll(() => page.evaluate(() => (
        window as typeof window & { __inertiaDocumentMarker?: string }
    ).__inertiaDocumentMarker)).toBe(documentMarker);

    await page.getByRole('link', { name: 'Revenir au socle Vue' }).click();
    await page.getByRole('button', { name: 'Valider avec Laravel' }).click();

    await expect(page.getByRole('alert')).toHaveText('Saisissez un nom pour tester la validation Laravel.');
    await expect(page.getByLabel('Nom du test')).toHaveAttribute('aria-invalid', 'true');

    const viewport = await page.evaluate(() => ({
        clientWidth: document.documentElement.clientWidth,
        scrollWidth: document.documentElement.scrollWidth,
    }));
    const accessibility = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
        .analyze();
    expect(viewport.scrollWidth).toBeLessThanOrEqual(viewport.clientWidth + 1);
    expect(accessibility.violations.filter((violation) => violation.impact === 'critical')).toEqual([]);

    await page.getByRole('link', { name: 'Shell 5.1' }).click();
    await expect(page).toHaveURL(/\/login$/);
});

test('phase three kView Vue primitives render and respond accessibly', async ({ page }, testInfo) => {
    const runtimeErrors: string[] = [];

    page.on('console', (message) => {
        if (message.type() === 'error') {
            runtimeErrors.push(message.text());
        }
    });
    page.on('pageerror', (error) => runtimeErrors.push(error.message));

    await page.goto('/__kview/inertia/components');

    await expect(page.getByRole('heading', { name: 'Recherche globale' })).toBeVisible();
    await expect(page.getByText('Même contrat visuel')).toBeVisible();

    await page.getByLabel('Terme de recherche').fill('Growth Europe');
    await page.getByRole('button', { name: 'Rechercher' }).click();

    await expect(page.getByText('Interaction Vue vérifiée')).toBeVisible();
    await expect(page.getByText(/Growth Europe/)).toBeVisible();

    await page.getByRole('button', { name: 'Fermer l’alerte' }).click();
    await expect(page.getByText('Interaction Vue vérifiée')).toBeHidden();

    const tabs = page.getByRole('tablist', { name: 'Sections du fund' });
    const overviewTab = tabs.getByRole('tab', { name: 'Overview' });
    const performanceTab = tabs.getByRole('tab', { name: /Performance/ });
    const activityTab = tabs.getByRole('tab', { name: 'Activity' });

    await overviewTab.focus();
    await overviewTab.press('End');
    await expect(activityTab).toBeFocused();
    await expect(activityTab).toHaveAttribute('aria-selected', 'true');

    await activityTab.press('Home');
    await expect(overviewTab).toBeFocused();

    await overviewTab.press('ArrowRight');
    await expect(performanceTab).toBeFocused();
    await expect(performanceTab).toHaveAttribute('aria-selected', 'true');
    await expect(page.getByRole('tabpanel', { name: /Performance/ })).toContainText('net IRR');

    await page.getByRole('group', { name: 'Période' }).getByRole('button', { name: '1Y' }).click();

    const compactMode = page.getByRole('switch', { name: /Mode compact/ });
    await expect(compactMode).toBeChecked();

    await page.locator('.ui-toggle-track').click();
    await expect(compactMode).not.toBeChecked();

    await compactMode.focus();
    await compactMode.press('Space');
    await expect(compactMode).toBeChecked();

    await compactMode.press('Space');
    await expect(compactMode).not.toBeChecked();

    await expect(page.locator('.kview-vue-showcase__choice-summary')).toContainText('performance · one-year · comfortable');
    await expect(page.getByRole('group', { name: 'Actions d’export' }).getByRole('button')).toHaveCount(2);
    await expect(page.locator('[data-ui-tabs], [data-ui-segment-value]')).toHaveCount(0);

    await page.getByLabel('Commitment').fill('2500000');
    await page.getByLabel('Ownership').fill('51.5');
    await page.getByLabel('Note').fill('Revue annuelle');
    await expect(page.getByRole('button', { name: 'Tester les adaptateurs' })).toBeVisible();

    const tooltipTrigger = page.getByRole('button', { name: 'Inspecter' });
    const tooltip = page.getByRole('tooltip', { name: 'Action secondaire accessible au focus' });

    await tooltipTrigger.focus();
    await expect(tooltip).toBeVisible();
    await tooltipTrigger.press('Escape');
    await expect(tooltip).toBeHidden();

    if (testInfo.project.name === 'mobile-chromium') {
        await tooltipTrigger.dispatchEvent('pointerenter', { pointerType: 'touch' });
        await expect(tooltip).toBeHidden();
    } else {
        await tooltipTrigger.hover();
        await expect(tooltip).toBeVisible();
        await page.mouse.move(0, 0);
        await expect(tooltip).toBeHidden();
    }

    const viewport = await page.evaluate(() => ({
        clientWidth: document.documentElement.clientWidth,
        scrollWidth: document.documentElement.scrollWidth,
    }));
    const accessibility = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
        .analyze();
    expect(viewport.scrollWidth).toBeLessThanOrEqual(viewport.clientWidth + 1);
    expect(accessibility.violations).toEqual([]);
    expect(runtimeErrors).toEqual([]);
});

test('phase five persistent layout keeps shell state, navigation and focus accessible', async ({ page }, testInfo) => {
    test.skip(!email || !password, 'Set E2E_EMAIL and E2E_PASSWORD to run the authenticated shell.');

    const runtimeErrors: string[] = [];

    page.on('console', (message) => {
        if (message.type() === 'error') {
            runtimeErrors.push(message.text());
        }
    });
    page.on('pageerror', (error) => runtimeErrors.push(error.message));

    await page.goto('/login');
    await page.getByRole('textbox', { name: 'Username', exact: true }).fill(email!);
    await page.getByLabel('Password').fill(password!);
    await page.getByRole('button', { name: 'Login' }).click();
    await page.goto('/__kview/inertia/application-shell');

    await expect(page.getByRole('heading', { name: 'Layout applicatif persistant' })).toBeVisible();
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');
    await expect(page.locator('nav[aria-label="Navigation principale"]')).toBeAttached();

    const shellMarker = await page.locator('.application-shell').evaluate((shell) => {
        const marker = crypto.randomUUID();
        (shell as HTMLElement & { __kview_investorPersistentMarker?: string }).__kview_investorPersistentMarker = marker;

        return marker;
    });

    await page.route('**/search/suggest?*', async (route) => {
        const query = new URL(route.request().url()).searchParams.get('q') ?? '';

        await route.fulfill({
            status: 200,
            contentType: 'application/json',
            body: JSON.stringify({
                query,
                groups: [
                    {
                        label: 'General Partners',
                        items: [
                            {
                                title: 'Demo Northstar Partners S.a r.l.',
                                eyebrow: 'GP',
                                meta: 'Demo Northstar Partners',
                                icon: 'building',
                                url: '/gps/phase-5-2-proof',
                            },
                        ],
                    },
                ],
                actions: [
                    {
                        title: `Search all records for "${query}"`,
                        eyebrow: 'Search',
                        meta: 'GPs, funds, investors',
                        icon: 'search',
                        url: `/search?q=${encodeURIComponent(query)}`,
                        inertia: true,
                    },
                ],
            }),
        });
    });

    await page.keyboard.press('Control+K');
    const globalSearchTrigger = page.locator('.application-shell__search');
    const globalSearch = page.getByRole('combobox', { name: 'Rechercher' });
    const globalSearchPanel = page.getByRole('dialog', { name: 'Recherche globale' });
    await expect(globalSearchTrigger).toHaveAttribute('aria-expanded', 'true');
    await expect(globalSearch).toBeFocused();
    await expect(globalSearch).toHaveAttribute('aria-expanded', 'true');
    await globalSearch.fill('Northstar');
    await expect(globalSearchPanel.getByText('Demo Northstar Partners S.a r.l.')).toBeVisible();

    for (let index = 0; index < 4; index += 1) {
        await globalSearch.press('ArrowDown');
    }

    const activeOptionId = await globalSearch.getAttribute('aria-activedescendant');
    expect(activeOptionId).toBeTruthy();
    await expect(page.locator(`#${activeOptionId}`)).toContainText('Demo Northstar Partners S.a r.l.');
    await expect(page.locator(`#${activeOptionId}`)).toHaveAttribute('aria-selected', 'true');
    await expect(globalSearch).toBeFocused();

    const panelGeometry = await globalSearchPanel.evaluate((element) => {
        const bounds = element.getBoundingClientRect();
        const visualViewport = window.visualViewport;

        return {
            left: bounds.left,
            right: bounds.right,
            top: bounds.top,
            bottom: bounds.bottom,
            width: bounds.width,
            viewportLeft: visualViewport?.offsetLeft ?? 0,
            viewportTop: visualViewport?.offsetTop ?? 0,
            viewportWidth: visualViewport?.width ?? document.documentElement.clientWidth,
            viewportHeight: visualViewport?.height ?? document.documentElement.clientHeight,
        };
    });
    expect(panelGeometry.left).toBeGreaterThanOrEqual(panelGeometry.viewportLeft + 9);
    expect(panelGeometry.right).toBeLessThanOrEqual(
        panelGeometry.viewportLeft + panelGeometry.viewportWidth - 9,
    );
    expect(panelGeometry.top).toBeGreaterThanOrEqual(panelGeometry.viewportTop + 9);
    expect(panelGeometry.bottom).toBeLessThanOrEqual(
        panelGeometry.viewportTop + panelGeometry.viewportHeight - 9,
    );
    expect(panelGeometry.width).toBeGreaterThan(0);

    const openAccessibility = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
        .analyze();
    const openSearchScreenshot = testInfo.outputPath('phase5-2-global-search-open.png');
    await page.screenshot({ path: openSearchScreenshot, fullPage: false });
    await testInfo.attach('phase5-2-global-search-open', {
        path: openSearchScreenshot,
        contentType: 'image/png',
    });
    expect(openAccessibility.violations).toEqual([]);

    await globalSearch.press('Escape');
    await expect(globalSearchTrigger).toHaveAttribute('aria-expanded', 'false');
    await expect(globalSearch).toHaveAttribute('aria-expanded', 'false');
    await expect(globalSearchTrigger).toBeFocused();

    const accountTrigger = page.getByRole('button', { name: /Compte de/ });
    await accountTrigger.click();
    await expect(page.getByRole('button', { name: 'Close account menu' })).toBeFocused();
    await page.keyboard.press('Escape');
    await expect(accountTrigger).toBeFocused();
    await accountTrigger.click();
    await page.keyboard.press('Control+K');
    await expect(page.getByRole('button', { name: 'Sign out' })).toBeHidden();
    await expect(globalSearch).toBeFocused();

    if (testInfo.project.name === 'mobile-chromium') {
        const toggle = page.getByRole('button', { name: 'Afficher la navigation' });

        await expect(page.locator('#application-navigation')).toHaveAttribute('inert', '');
        await toggle.click();
        await expect(
            page.locator('#application-navigation').getByRole('button', { name: 'Fermer la navigation' }),
        ).toBeFocused();
        await expect(page.locator('.application-shell__topbar')).toHaveAttribute('inert', '');
        await expect(page.locator('#application-content')).toHaveAttribute('inert', '');

        const drawer = page.getByRole('dialog', { name: 'Navigation principale' });
        const brand = drawer.getByRole('link', { name: 'kView INVESTOR by kView' });
        const settings = drawer.getByRole('link', { name: 'Settings' });

        await settings.focus();
        await page.keyboard.press('Tab');
        await expect(brand).toBeFocused();
        await page.keyboard.press('Shift+Tab');
        await expect(settings).toBeFocused();
        await page.keyboard.press('Escape');
        await expect(toggle).toBeFocused();
        await expect(page.locator('.ui-tooltip__content.is-open')).toHaveCount(0);
    } else {
        const toggle = page.getByRole('button', { name: 'Masquer la navigation' });

        await toggle.click();
        await expect(page.getByRole('button', { name: 'Afficher la navigation' })).toBeVisible();
    }

    await page.getByRole('link', { name: 'Tester la persistance' }).click();
    await expect(page).toHaveURL(/\/__kview\/inertia\/application-shell\/secondary$/);
    await expect(page.getByRole('heading', { name: 'Seconde page du layout' })).toBeVisible();
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __kview_investorPersistentMarker?: string }
    ).__kview_investorPersistentMarker)).toBe(shellMarker);

    if (testInfo.project.name === 'desktop-chromium') {
        await expect(page.getByRole('button', { name: 'Afficher la navigation' })).toBeVisible();
    }

    await page.getByRole('link', { name: 'Revenir au layout' }).click();
    await expect(page).toHaveURL(/\/__kview\/inertia\/application-shell$/);
    await expect(page.getByRole('heading', { name: 'Layout applicatif persistant' })).toBeVisible();
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __kview_investorPersistentMarker?: string }
    ).__kview_investorPersistentMarker)).toBe(shellMarker);

    const viewport = await page.evaluate(() => ({
        clientWidth: document.documentElement.clientWidth,
        scrollWidth: document.documentElement.scrollWidth,
    }));
    const accessibility = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
        .analyze();
    const shellScreenshot = testInfo.outputPath('phase5-1-persistent-application-shell.png');

    await page.screenshot({ path: shellScreenshot, fullPage: true });
    await testInfo.attach('phase5-1-persistent-application-shell', {
        path: shellScreenshot,
        contentType: 'image/png',
    });

    expect(viewport.scrollWidth).toBeLessThanOrEqual(viewport.clientWidth + 1);
    expect(accessibility.violations).toEqual([]);
    expect(runtimeErrors).toEqual([]);

    await accountTrigger.click();
    await page.getByRole('button', { name: 'Sign out' }).click();
    await expect(page).toHaveURL(/\/login$/);
    await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
});

test('an expired Inertia session leaves the persistent root for the Blade login page', async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== 'desktop-chromium', 'The session boundary only needs one browser project.');
    test.skip(!email || !password, 'Set E2E_EMAIL and E2E_PASSWORD to validate session expiry.');

    await page.goto('/login');
    await page.getByRole('textbox', { name: 'Username', exact: true }).fill(email!);
    await page.getByLabel('Password').fill(password!);
    await page.getByRole('button', { name: 'Login' }).click();
    await page.goto('/__kview/inertia/application-shell');
    await expect(page.getByRole('heading', { name: 'Layout applicatif persistant' })).toBeVisible();

    await page.context().clearCookies();
    await page.getByRole('link', { name: 'Tester la persistance' }).click();

    await expect(page).toHaveURL(/\/login$/);
    await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
    await expect(page.locator('html')).not.toHaveAttribute('data-frontend-root', 'inertia');
});

test('the persistent Vue shell crosses to product search with delayed content feedback and no reload', async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== 'desktop-chromium', 'The persistent cross-path transition only needs one browser project.');
    test.skip(!email || !password, 'Set E2E_EMAIL and E2E_PASSWORD to validate the persistent search transition.');

    await page.goto('/login');
    await page.getByRole('textbox', { name: 'Username', exact: true }).fill(email!);
    await page.getByLabel('Password').fill(password!);
    await page.getByRole('button', { name: 'Login' }).click();
    await page.goto('/__kview/inertia/application-shell');

    const documentMarker = await page.evaluate(() => {
        const marker = crypto.randomUUID();

        (window as typeof window & { __technicalShellDocument?: string }).__technicalShellDocument = marker;

        return marker;
    });
    const shellMarker = await page.locator('.application-shell').evaluate((shell) => {
        const marker = crypto.randomUUID();

        (shell as HTMLElement & { __technicalShellMarker?: string }).__technicalShellMarker = marker;

        return marker;
    });
    let releaseNavigation = (): void => {};
    const navigationGate = new Promise<void>((resolve) => {
        releaseNavigation = resolve;
    });

    await page.route('**/search?*', async (route) => {
        if (route.request().headers()['x-inertia'] === 'true') {
            await navigationGate;
        }

        await route.continue();
    });

    await page.getByRole('combobox', { name: 'Rechercher' }).fill('demo');
    await page.getByRole('combobox', { name: 'Rechercher' }).press('Enter');

    await expect(page.locator('#application-content')).toHaveAttribute('aria-busy', 'true');
    await expect(page.getByRole('status', { name: 'Chargement de la page' })).toBeVisible();
    await expect(page.getByRole('heading', { name: 'Layout applicatif persistant' })).toBeVisible();
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __technicalShellMarker?: string }
    ).__technicalShellMarker)).toBe(shellMarker);

    releaseNavigation();
    await expect(page).toHaveURL(/\/search\?q=demo$/);
    await expect(page.getByRole('heading', { name: 'Search' })).toBeVisible();
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');
    await expect(page.locator('#application-navigation')).toBeAttached();
    await expect(page.locator('#top-menu, #left-menu')).toHaveCount(0);
    await expect(page.locator('.application-shell__navigation-skeleton')).toHaveCount(0);
    await expect.poll(() => page.evaluate(() => (
        window as typeof window & { __technicalShellDocument?: string }
    ).__technicalShellDocument)).toBe(documentMarker);
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __technicalShellMarker?: string }
    ).__technicalShellMarker)).toBe(shellMarker);
    await page.unroute('**/search?*');
});

test('phase three remains stable at constrained desktop and tablet widths', async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== 'desktop-chromium', 'Intermediate widths run once in desktop Chromium.');

    for (const viewport of [
        { name: 'constrained', width: 1024, height: 900 },
        { name: 'tablet', width: 768, height: 1024 },
    ]) {
        await page.setViewportSize({ width: viewport.width, height: viewport.height });
        await page.goto('/__kview/inertia/components');

        await expect(page.getByRole('tablist', { name: 'Sections du fund' })).toBeVisible();
        await expect(page.getByRole('group', { name: 'Période' })).toBeVisible();
        await expect(page.getByRole('switch', { name: /Mode compact/ })).toBeVisible();

        const metrics = await page.evaluate(() => ({
            clientWidth: document.documentElement.clientWidth,
            scrollWidth: document.documentElement.scrollWidth,
        }));

        expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth + 1);

        const screenshot = testInfo.outputPath(`phase3-${viewport.name}.png`);
        await page.screenshot({ path: screenshot, fullPage: true });
        await testInfo.attach(`phase3-${viewport.name}`, {
            path: screenshot,
            contentType: 'image/png',
        });
    }
});

test('Component Lab proves Blade and Vue parity in their real isolated roots', async ({ page }) => {
    const runtimeErrors: string[] = [];

    page.on('console', (message) => {
        if (message.type() === 'error') {
            runtimeErrors.push(message.text());
        }
    });
    page.on('pageerror', (error) => runtimeErrors.push(error.message));

    await page.goto('/__kview#vue-parity');

    const parity = page.locator('#vue-parity');
    await expect(parity.getByRole('heading', { name: 'Deux moteurs, un seul contrat kView.' })).toBeVisible();

    const bladeTabs = parity.getByRole('tablist', { name: 'Sections du fund' });
    const bladeOverview = bladeTabs.getByRole('tab', { name: 'Overview' });
    const bladePerformance = bladeTabs.getByRole('tab', { name: /Performance/ });

    await bladeOverview.focus();
    await bladeOverview.press('ArrowRight');
    await expect(bladePerformance).toBeFocused();
    await expect(bladePerformance).toHaveAttribute('aria-selected', 'true');
    await expect(parity.getByRole('tabpanel', { name: /Performance/ })).toContainText('net IRR');

    const vue = page.frameLocator('iframe[title="Primitives kView rendues par Vue"]');
    await expect(vue.getByRole('heading', { name: 'Recherche globale' })).toBeVisible();

    const vueTabs = vue.getByRole('tablist', { name: 'Sections du fund' });
    await vueTabs.getByRole('tab', { name: /Performance/ }).click();
    await expect(vue.getByRole('tabpanel', { name: /Performance/ })).toContainText('net IRR');
    await expect(vue.locator('[data-ui-tabs], [data-ui-segment-value]')).toHaveCount(0);

    const parentMetrics = await page.evaluate(() => {
        const originalScrollX = window.scrollX;

        window.scrollTo({ left: 999, top: window.scrollY });
        const reachableScrollX = window.scrollX;
        window.scrollTo({ left: originalScrollX, top: window.scrollY });

        return {
            clientWidth: document.documentElement.clientWidth,
            bodyScrollWidth: document.body.scrollWidth,
            reachableScrollX,
        };
    });
    const vueMetrics = await vue.locator('html').evaluate((element) => ({
        clientWidth: element.clientWidth,
        scrollWidth: element.scrollWidth,
    }));
    const accessibility = await new AxeBuilder({ page })
        .include('#vue-parity')
        .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
        .analyze();

    expect(parentMetrics.bodyScrollWidth).toBeLessThanOrEqual(parentMetrics.clientWidth + 1);
    expect(parentMetrics.reachableScrollX).toBe(0);
    expect(vueMetrics.scrollWidth).toBeLessThanOrEqual(vueMetrics.clientWidth + 1);
    expect(accessibility.violations).toEqual([]);
    expect(runtimeErrors).toEqual([]);
});
