import { test, expect } from '@playwright/test';
import {
    BASE_URL,
    USER,
    ADMIN,
    loginAs,
    ensureLiveRacingEnabled,
    createArena,
    createEventShowUrl,
    addRace,
    addHorse,
    addHorses,
    offerSingleBetTypes,
    offerMultiBetTypeCombos,
    publishEvent,
} from '../helpers';

// Regression guard for the mobile betting wizard: selecting a multi-combo bet
// type (Daily Double offered as several race pairs) must not make the document
// wider than the viewport / let the page pan sideways into dead space, and
// clicking a combo pill must not jump the page to the top either.
// The horizontal bug: `.visually-hidden-input` (the wrapped combo radios)
// sitting at its static x≈800px position inside the horizontally-scrolled
// .wizard-combo-tabs. The vertical bug: fixing that by pinning the hidden
// radio to `left: 0; top: 0;` with no `position: relative` on its label
// ancestor left it pinned to the *document* origin, so clicking the label
// (which natively focuses the wrapped radio) made the browser scroll the
// whole page to (0, 0). See templates/play/live-racing/event.html.twig
// (.visually-hidden-input, .wizard-tab).

test.describe('Live Racing wizard — no unwanted page scroll on mobile', () => {
    test.setTimeout(180_000);
    test.use({ viewport: { width: 390, height: 844 } });

    test('multi-combo Daily Double panel does not widen the page', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await ensureLiveRacingEnabled(page);

        const arena = await createArena(page, 'LRHScroll', { win: '10', daily_double: '10' });
        const { eventUrl, eventId } = await createEventShowUrl(page, arena);
        const r1 = await addRace(page, eventUrl, 1);
        const r2 = await addRace(page, eventUrl, 2);
        const r3 = await addRace(page, eventUrl, 3);
        await addHorses(page, r1.raceUrl, 6);
        await addHorses(page, r2.raceUrl, 6);
        await addHorses(page, r3.raceUrl, 6);
        await offerSingleBetTypes(page, eventUrl, [{ key: 'win', raceId: r1.raceId }]);
        // Two combos → .wizard-combo-tabs renders (the case that reproduced the bug).
        await offerMultiBetTypeCombos(page, eventUrl, 'daily_double', [
            [r1.raceId, r2.raceId],
            [r2.raceId, r3.raceId],
        ]);
        await publishEvent(page, eventUrl);

        await loginAs(page, USER.mobile, USER.password);
        await page.goto(`${BASE_URL}/play/live-racing/${eventId}`);

        await page.click('[data-testid="tab-bet-key-daily_double"]');
        await expect(page.locator('[data-testid="tab-bet-key-daily_double"]')).toHaveClass(/is-active/);
        await expect(page.locator('[data-testid^="wizard-combo-tabs-"]')).toBeVisible();

        const metrics = await page.evaluate(() => {
            const de = document.documentElement;
            de.scrollLeft = 9999;
            const clampedScrollLeft = de.scrollLeft;
            de.scrollLeft = 0;
            return {
                clampedScrollLeft,
                scrollWidth: de.scrollWidth,
                clientWidth: de.clientWidth,
                overflowX: getComputedStyle(document.body).overflowX,
                walletExceedsViewport:
                    document.querySelector('.wallet-bar')!.getBoundingClientRect().width >
                    de.clientWidth + 1,
            };
        });

        // The real fix: the document itself is not wider than the viewport.
        expect(metrics.scrollWidth).toBe(metrics.clientWidth);
        expect(metrics.clampedScrollLeft).toBe(0);
        expect(metrics.overflowX).toBe('hidden');
        expect(metrics.walletExceedsViewport).toBe(false);
    });

    test('clicking a combo pill does not scroll the page to the top', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await ensureLiveRacingEnabled(page);

        const arena = await createArena(page, 'LRVScroll', { win: '10', daily_double: '10' });
        const { eventUrl, eventId } = await createEventShowUrl(page, arena);
        const r1 = await addRace(page, eventUrl, 1);
        const r2 = await addRace(page, eventUrl, 2);
        const r3 = await addRace(page, eventUrl, 3);
        await addHorses(page, r1.raceUrl, 6);
        await addHorses(page, r2.raceUrl, 6);
        await addHorses(page, r3.raceUrl, 6);
        await offerSingleBetTypes(page, eventUrl, [{ key: 'win', raceId: r1.raceId }]);
        // Two combos → .wizard-combo-tabs renders with real, clickable radio labels.
        await offerMultiBetTypeCombos(page, eventUrl, 'daily_double', [
            [r1.raceId, r2.raceId],
            [r2.raceId, r3.raceId],
        ]);
        await publishEvent(page, eventUrl);

        await loginAs(page, USER.mobile, USER.password);
        await page.goto(`${BASE_URL}/play/live-racing/${eventId}`);

        await page.click('[data-testid="tab-bet-key-daily_double"]');
        await expect(page.locator('[data-testid^="wizard-combo-tabs-"]')).toBeVisible();

        // Scroll down first so a jump back to the top is detectable. The page must
        // actually be taller than the viewport for this to mean anything.
        const scrollable = await page.evaluate(() => document.documentElement.scrollHeight > window.innerHeight + 50);
        expect(scrollable).toBe(true);
        await page.evaluate(() => window.scrollTo(0, 200));
        // scrollTo() doesn't always settle within the same tick — wait for it to land exactly.
        await page.waitForFunction(() => window.scrollY === 200);
        const scrollYBefore = await page.evaluate(() => window.scrollY);
        expect(scrollYBefore).toBe(200);

        await page.locator('[data-testid^="tab-combo-"]').first().click();
        await expect(page.locator('[data-testid^="tab-combo-"]').first().locator('input')).toBeChecked();
        // Give any (unwanted) scroll-into-view a moment to settle before asserting it didn't happen.
        await page.waitForTimeout(300);

        const scrollYAfter = await page.evaluate(() => window.scrollY);
        expect(scrollYAfter).toBe(200);
    });
});

// Horse rows: the position buttons (1st..6th) must never need a sideways swipe.
// On phones they always sit on their own line below the jockey/trainer as
// equal-width columns; on wide screens they stay inline unless the horse's
// details are too long, in which case that row stacks the same way.
// See event.html.twig `.horse-row__scroll` / `.horse-row__pinned`.
const LONG_NAME = 'Magnificent Thunderbolt Of The Northern Highlands Express Who Runs Faster Than The Wind Over Manila Bay';

async function setUpSuperSixEvent(page: import('@playwright/test').Page): Promise<string> {
    await loginAs(page, ADMIN.mobile, ADMIN.password);
    await ensureLiveRacingEnabled(page);
    const arena = await createArena(page, 'LRStackPos', { super_six: '10' });
    const { eventUrl, eventId } = await createEventShowUrl(page, arena);
    const r1 = await addRace(page, eventUrl, 1);
    await addHorses(page, r1.raceUrl, 6);
    await addHorse(page, r1.raceUrl, 7, LONG_NAME);
    await offerSingleBetTypes(page, eventUrl, [{ key: 'super_six', raceId: r1.raceId }]);
    await publishEvent(page, eventUrl);

    await loginAs(page, USER.mobile, USER.password);
    await page.goto(`${BASE_URL}/play/live-racing/${eventId}`);
    await page.click('[data-testid="tab-bet-key-super_six"]');
    await expect(page.locator('[data-testid="key-panel-super_six"] .horse-row').first()).toBeVisible();
    return eventId;
}

type RowLayout = {
    name: string;
    scrollWidth: number;
    clientWidth: number;
    stacked: boolean;
    btnWidths: number[];
    btnTops: number[];
    positionsWidth: number;
    innerWidth: number;
    nameTruncated: boolean;
};

async function rowLayouts(page: import('@playwright/test').Page): Promise<RowLayout[]> {
    return page.locator('[data-testid="key-panel-super_six"] .horse-row').evaluateAll(rows => rows.map(row => {
        const scroll = row.querySelector('.horse-row__scroll') as HTMLElement;
        const details = row.querySelector('.horse-row__details')!.getBoundingClientRect();
        const positions = row.querySelector('.horse-row__positions')!.getBoundingClientRect();
        const name = row.querySelector('.horse-row__name') as HTMLElement;
        const btns = Array.from(row.querySelectorAll('.horse-row__pos-btn')).map(b => b.getBoundingClientRect());
        const cs = getComputedStyle(scroll);
        return {
            name: name.textContent!.trim(),
            scrollWidth: scroll.scrollWidth,
            clientWidth: scroll.clientWidth,
            stacked: positions.top >= details.bottom - 1,
            btnWidths: btns.map(b => b.width),
            btnTops: btns.map(b => Math.round(b.top)),
            positionsWidth: positions.width,
            innerWidth: scroll.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight),
            nameTruncated: name.scrollWidth > name.clientWidth + 1,
        };
    }));
}

test.describe('Live Racing wizard — horse position buttons (mobile)', () => {
    test.setTimeout(180_000);
    test.use({ viewport: { width: 360, height: 800 } });

    test('buttons sit below the details in equal columns, no sideways swipe', async ({ page }) => {
        await setUpSuperSixEvent(page);
        const layouts = await rowLayouts(page);
        expect(layouts.length).toBe(7);

        for (const l of layouts) {
            expect(l.scrollWidth, `${l.name}: no horizontal overflow`).toBeLessThanOrEqual(l.clientWidth);
            expect(l.stacked, `${l.name}: buttons below details`).toBe(true);
            expect(l.btnWidths).toHaveLength(6);
            expect(new Set(l.btnTops).size, `${l.name}: all 6 on one line`).toBe(1);
            const w = l.btnWidths[0];
            for (const bw of l.btnWidths) expect(Math.abs(bw - w)).toBeLessThanOrEqual(1);
            expect(l.positionsWidth, `${l.name}: columns span the row`).toBeGreaterThanOrEqual(l.innerWidth - 1);
            expect(l.nameTruncated, `${l.name}: name not cut off`).toBe(false);
        }
        expect(layouts.map(l => l.name)).toContain(LONG_NAME);

        const doc = await page.evaluate(() => ({ sw: document.documentElement.scrollWidth, cw: document.documentElement.clientWidth }));
        expect(doc.sw).toBe(doc.cw);

        // Still bettable: tap 1st..6th across the stacked rows.
        const panel = page.locator('[data-testid="key-panel-super_six"]');
        await panel.locator('.horse-row').nth(6).locator('.horse-row__pos-btn').nth(5).click();
        await expect(panel.locator('.horse-row').nth(6).locator('.horse-row__pos-btn input').nth(5)).toBeChecked();
        await panel.locator('.horse-row').last().scrollIntoViewIfNeeded();
    });
});

test.describe('Live Racing wizard — horse position buttons (desktop)', () => {
    test.setTimeout(180_000);
    test.use({ viewport: { width: 1280, height: 900 } });

    test('short details stay inline; a too-long name stacks its row', async ({ page }) => {
        await setUpSuperSixEvent(page);
        const layouts = await rowLayouts(page);

        for (const l of layouts) {
            expect(l.scrollWidth, `${l.name}: no horizontal overflow`).toBeLessThanOrEqual(l.clientWidth);
            expect(l.nameTruncated, `${l.name}: name not cut off`).toBe(false);
        }
        const short = layouts.find(l => l.name === 'Horse 1')!;
        const long = layouts.find(l => l.name === LONG_NAME)!;
        expect(short.stacked).toBe(false);
        expect(long.stacked).toBe(true);
        expect(new Set(long.btnTops).size).toBe(1);
        await page.locator('[data-testid="key-panel-super_six"] .horse-row').last().scrollIntoViewIfNeeded();
    });
});
