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

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

if (process.env.CI && (!email || !password)) {
    throw new Error('E2E_EMAIL and E2E_PASSWORD are required for the Phase 6 exchange-rate route in CI.');
}

const login = async (page: Page): Promise<void> => {
    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 expect(page).toHaveURL(/\/$/);
};

const expectPersistentShell = async (page: Page): Promise<void> => {
    await expect(page.locator('.application-shell')).toHaveCount(1);
    await expect(page.locator('#application-navigation')).toHaveCount(1);
    await expect(page.locator('#application-content')).toHaveCount(1);
    await expect(page.locator('#top-menu, #left-menu')).toHaveCount(0);
};

const openFirstExchangeRate = async (page: Page): Promise<string> => {
    await page.goto('/exchange-rates');
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');
    await expectPersistentShell(page);

    const detailLink = page
        .locator('#exchange-rates-crud-table tbody a.ui-visually-hidden[href*="/exchange-rates/"]')
        .first();

    await expect(detailLink).toBeAttached();
    const href = await detailLink.getAttribute('href');

    expect(href).toMatch(/\/exchange-rates\/[0-9a-f-]+$/);
    const shellMarker = await page.locator('.application-shell').evaluate((shell) => {
        const marker = crypto.randomUUID();
        (shell as HTMLElement & { __phaseSevenExchangeMarker?: string }).__phaseSevenExchangeMarker = marker;

        return marker;
    });
    await page.locator('#exchange-rates-crud-table tbody tr').first().press('Enter');
    await expect(page).toHaveURL(/\/exchange-rates\/[0-9a-f-]+$/);
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __phaseSevenExchangeMarker?: string }
    ).__phaseSevenExchangeMarker)).toBe(shellMarker);

    return href!;
};

test('Phase 6 exchange-rate detail preserves the Blade UI and accessible interactions', async ({ page }, testInfo) => {
    test.skip(!email || !password, 'Set E2E_EMAIL and E2E_PASSWORD to validate the Phase 6 exchange-rate route.');

    const consoleProblems: string[] = [];

    page.on('console', (message) => {
        if (message.type() === 'error' || message.type() === 'warning') {
            consoleProblems.push(`${message.type()}: ${message.text()}`);
        }
    });
    page.on('pageerror', (error) => consoleProblems.push(`pageerror: ${error.message}`));

    await login(page);
    await openFirstExchangeRate(page);
    await expectPersistentShell(page);

    const title = page.getByRole('heading', { level: 1 });
    const cards = page.locator('.info-card');

    await expect(title).toHaveText(/^[A-Z]{3} \/ [A-Z]{3}$/);
    await expect(page.getByRole('link', { name: 'Back to currencies' })).toBeVisible();
    await expect(page.getByRole('link', { name: 'Edit', exact: true })).toBeVisible();
    await expect(page.getByRole('button', { name: 'Delete', exact: true })).toBeVisible();
    await expect(cards).toHaveCount(5);
    await expect(cards.locator('.info-card-label')).toHaveText([
        'From',
        'To',
        'Rate',
        'Date',
        'Source',
    ]);

    const geometry = await page.evaluate(() => {
        const content = document.querySelector('#application-content')?.getBoundingClientRect();
        const card = document.querySelector('.exchange-rate-page__card')?.getBoundingClientRect();
        const title = document.querySelector('.ui-page-header__heading')?.getBoundingClientRect();
        const actions = document.querySelector('.ui-page-header__actions')?.getBoundingClientRect();
        const infoCards = Array.from(document.querySelectorAll('.info-card'))
            .map((element) => element.getBoundingClientRect())
            .map(({ x, y, width, height }) => ({ x, y, width, height }));

        return content && card ? {
            leftInset: card.left - content.left,
            rightInset: content.right - card.right,
            cardWidth: card.width,
            actionGap: title && actions ? actions.left - title.right : null,
            infoCards,
        } : null;
    });

    expect(geometry).not.toBeNull();
    expect(geometry?.leftInset ?? 0).toBeGreaterThanOrEqual(15);
    expect(geometry?.rightInset ?? 0).toBeGreaterThanOrEqual(15);
    expect(geometry?.actionGap).not.toBeNull();
    expect(geometry?.actionGap ?? Number.POSITIVE_INFINITY).toBeLessThanOrEqual(64);
    expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth + 1)).toBe(true);

    const infoCardGeometry = geometry?.infoCards ?? [];

    if (testInfo.project.name === 'desktop-chromium') {
        expect(infoCardGeometry.slice(0, 4).every((card) => (
            Math.abs(card.y - infoCardGeometry[0].y) <= 1
            && Math.abs(card.width - infoCardGeometry[0].width) <= 1
        ))).toBe(true);
        expect(infoCardGeometry[4].y).toBeGreaterThan(infoCardGeometry[0].y + infoCardGeometry[0].height);
        expect(infoCardGeometry[0].width).toBeGreaterThanOrEqual(220);
    } else {
        expect(infoCardGeometry.every((card, index) => (
            Math.abs(card.width - infoCardGeometry[0].width) <= 1
            && (index === 0 || card.y > infoCardGeometry[index - 1].y + infoCardGeometry[index - 1].height)
        ))).toBe(true);
    }

    const deleteButton = page.getByRole('button', { name: 'Delete', exact: true });

    await deleteButton.focus();
    await deleteButton.click();
    const dialog = page.getByRole('alertdialog', { name: 'Delete exchange rate?' });

    await expect(dialog).toBeVisible();
    await expect(dialog.getByRole('button', { name: 'Cancel' })).toBeFocused();
    const confirmationGeometry = await dialog.evaluate((element) => {
        const bounds = element.getBoundingClientRect();
        const overlay = element.closest('.ui-dialog-layer')?.querySelector<HTMLElement>('.ui-dialog-overlay');
        const overlayStyle = overlay ? getComputedStyle(overlay) : null;

        return {
            leftInset: bounds.left,
            rightInset: window.innerWidth - bounds.right,
            width: bounds.width,
            overlayBackground: overlayStyle?.backgroundColor ?? null,
            overlayFilter: overlayStyle?.backdropFilter ?? null,
        };
    });

    expect(confirmationGeometry.leftInset).toBeGreaterThanOrEqual(15);
    expect(confirmationGeometry.rightInset).toBeGreaterThanOrEqual(15);
    expect(confirmationGeometry.width).toBeLessThanOrEqual(448);
    expect(confirmationGeometry.overlayBackground).toBe('rgba(6, 12, 28, 0.34)');
    expect(confirmationGeometry.overlayFilter).toContain('blur(3px)');
    await page.keyboard.press('Escape');
    await expect(dialog).toBeHidden();
    await expect(deleteButton).toBeFocused();

    await deleteButton.click();
    await dialog.getByRole('button', { name: 'Cancel' }).click();
    await expect(dialog).toBeHidden();
    await expect(deleteButton).toBeFocused();

    const axe = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
        .analyze();
    const blockingViolations = axe.violations.filter((violation) => (
        violation.impact === 'critical' || violation.impact === 'serious'
    ));

    expect(blockingViolations).toEqual([]);
    await page.screenshot({
        path: testInfo.outputPath(`exchange-rate-phase6-${testInfo.project.name}.png`),
        fullPage: true,
    });
    expect(consoleProblems).toEqual([]);
});

test('Phase 6 exchange-rate detail keeps Edit and Back inside the persistent Vue shell', async ({ page }, testInfo) => {
    test.skip(testInfo.project.name !== 'desktop-chromium', 'The persistent Vue navigation boundary only needs one browser project.');
    test.skip(!email || !password, 'Set E2E_EMAIL and E2E_PASSWORD to validate the Phase 6 exchange-rate route.');

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

        return marker;
    });
    await page.getByRole('link', { name: 'Edit', exact: true }).click();
    await expect(page).toHaveURL(new RegExp(`${showUrl}/edit$`));
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');
    await expect(page.locator('.application-shell')).toHaveCount(1);
    await expect(page.locator('#top-menu, #left-menu')).toHaveCount(0);
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __phaseSevenExchangeMarker?: string }
    ).__phaseSevenExchangeMarker)).toBe(shellMarker);

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

        return marker;
    });
    await page.getByRole('link', { name: 'Back to currencies' }).click();
    await expect(page).toHaveURL(/\/exchange-rates$/);
    await expect(page.locator('html')).toHaveAttribute('data-frontend-root', 'inertia');
    await expectPersistentShell(page);
    await expect.poll(() => page.locator('.application-shell').evaluate((shell) => (
        shell as HTMLElement & { __phaseSevenExchangeMarker?: string }
    ).__phaseSevenExchangeMarker)).toBe(backMarker);
});
