import { test, expect, Page } from '@playwright/test';
import {
    BASE_URL,
    ADMIN,
    AUDITOR,
    FINANCE,
    OPERATIONS,
    loginAs,
    registerAndLogin,
    ensureLiveRacingEnabled,
    createArena,
    createEventShowUrl,
    addRace,
    addHorses,
    offerSingleBetTypes,
    publishEvent,
    placeWizardBet,
    closeBetting,
    scratchHorseBeforeClose,
    saveResult,
    settleAndMarkRace,
} from '../helpers';

/** Resolve an EventBetType id from a combination-entry block on a race result form. */
async function dividendRowId(page: Page, label: RegExp): Promise<string> {
    const blocks = page.locator('[data-testid^="combo-bettype-"]');
    for (let i = 0; i < (await blocks.count()); i++) {
        const text = (await blocks.nth(i).locator('label.fw-semibold').first().textContent())
            ?.replace(/\s+/g, ' ').replace(/\s*\([\d-]+…\)\s*$/u, '').trim() ?? '';
        if (label.test(text)) {
            return (await blocks.nth(i).getAttribute('data-testid'))?.replace('combo-bettype-', '') ?? '';
        }
    }
    throw new Error(`no combination block for ${label}`);
}

// The standalone wall-monitor dashboard for the current Live Racing event:
// /wfbo/live-racing/monitor (auto-picks today's published event) and
// /wfbo/live-racing/monitor/data (the bare 15s-poll fragment).

test.describe.configure({ mode: 'serial' });

const SETUP_TIMEOUT = 180_000;
const TEST_TIMEOUT  = 120_000;

test.describe('WFBO — Live Racing Monitor', () => {
    test.setTimeout(TEST_TIMEOUT);

    let eventId: string;
    let raceUrl: string;
    let horseIds: string[] = [];

    test.beforeAll(async ({ browser }) => {
        test.setTimeout(SETUP_TIMEOUT);
        const page = await browser.newPage();
        await loginAs(page, ADMIN.mobile, ADMIN.password);

        const arena = await createArena(page, 'LRMonitor', { win: '10', forecast: '10' });
        const created = await createEventShowUrl(page, arena);
        eventId = created.eventId;

        const race = await addRace(page, created.eventUrl, 1);
        raceUrl = race.raceUrl;
        horseIds = await addHorses(page, race.raceUrl, 4);

        await offerSingleBetTypes(page, created.eventUrl, [{ key: 'win', raceId: race.raceId }]);
        await publishEvent(page, created.eventUrl);
        await page.close();
    });

    const openMonitor = async (page: Page, qs = ''): Promise<void> => {
        await page.goto(`${BASE_URL}/wfbo/live-racing/monitor${qs}`);
    };

    test('renders the dashboard for the pinned event with KPIs, a race grid and controls', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await openMonitor(page, `?event=${eventId}`);

        await expect(page.locator('[data-testid="lr-monitor-live"]')).toBeAttached();
        await expect(page.locator('[data-testid="lr-monitor-kpis"]')).toBeVisible();
        await expect(page.locator('[data-testid="lr-monitor-kpis"] [data-testid="lr-monitor-kpi"]')).toHaveCount(11);
        // No bets placed against this fixture event yet — zero-state for both.
        const kpi = page.locator('[data-testid="lr-monitor-kpi"]');
        await expect(kpi.filter({ hasText: 'Win Rate Today' })).toContainText('0%');
        await expect(kpi.filter({ hasText: 'Active Players Today' })).toContainText('0');
        // No race has ever closed on this fixture event, so nothing can be "late".
        await expect(kpi.filter({ hasText: 'Late Bets' })).toContainText('0');
        await expect(page.locator('[data-testid^="lr-monitor-race-"][data-testid$="-late-bets"]')).toHaveCount(0);
        await expect(page.locator('[data-testid="lr-monitor-race-1"]')).toBeVisible();
        await expect(page.locator('[data-testid="lr-monitor-race-1"]')).toContainText('Open');
        // Betting is still open — the horse list only shows once betting closes.
        await expect(page.locator('[data-testid="lr-monitor-race-1-horses"]')).toHaveCount(0);
        await expect(page.locator('[data-testid="lr-monitor-fullscreen"]')).toBeVisible();
        await expect(page.locator('[data-testid="lr-monitor-updated"]')).toContainText('Updated');
        // Fixture-built event has no video URL.
        await expect(page.locator('[data-testid="lr-monitor-video-empty"]')).toBeVisible();
    });

    test('reflects race state: betting closed, scratched horse, then dividends saved', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);

        await scratchHorseBeforeClose(page, raceUrl, horseIds[0]);
        await closeBetting(page, raceUrl);

        await openMonitor(page, `?event=${eventId}`);
        let card = page.locator('[data-testid="lr-monitor-race-1"]');
        await expect(card).toContainText('Betting closed');
        await expect(card).toContainText('Scratched');
        // Result not saved yet — flagged as such, no "saved" tag.
        await expect(page.locator('[data-testid="lr-monitor-race-1-dividends-pending"]')).toBeVisible();
        await expect(page.locator('[data-testid="lr-monitor-race-1-dividends-saved"]')).toHaveCount(0);

        // Betting closed (not settled) — the horse list now appears beside the video,
        // listing every horse including the one scratched before close.
        const horses = page.locator('[data-testid="lr-monitor-race-1-horses"]');
        await expect(horses).toBeVisible();
        await expect(horses).toContainText('Horse 1');
        await expect(horses).toContainText('Horse 2');
        await expect(horses).toContainText('Horse 3');
        await expect(horses).toContainText('Horse 4');
        await expect(horses.locator('.lr-mon__horse--scratched')).toContainText('Horse 1');

        // Save a result → dividends stored → monitor just says "✓ Dividends saved"
        // (no combination detail) and the race flips to "Awaiting settlement".
        await page.goto(`${BASE_URL}${raceUrl}`);
        const winId = await dividendRowId(page, /^Win$/);
        await saveResult(page, raceUrl, {
            combinations: { [winId]: [{ horseIds: [horseIds[1]], dividend: '20.00' }] },
        });

        await openMonitor(page, `?event=${eventId}`);
        card = page.locator('[data-testid="lr-monitor-race-1"]');
        await expect(card).toContainText('Awaiting settlement');
        await expect(page.locator('[data-testid="lr-monitor-race-1-dividends-saved"]')).toBeVisible();
        await expect(card).not.toContainText('20.00');
        // Still not settled — the horse list stays visible through this state too.
        await expect(page.locator('[data-testid="lr-monitor-race-1-horses"]')).toBeVisible();

        // Settle the race — EventRace::isSettled() flips true, and the monitor
        // shows which horse won (the Win combination just saved above). The
        // "settling now" heartbeat can still be hot for a few seconds after
        // settleAndMarkRace() confirms the race is settled (see SETTLING_TTL
        // in WfboController), so assert on the winner line itself rather than
        // the status badge text.
        const raceId = raceUrl.split('/').pop() ?? '';
        await settleAndMarkRace(page, raceId);

        await openMonitor(page, `?event=${eventId}`);
        const winner = page.locator('[data-testid="lr-monitor-race-1-winner"]');
        await expect(winner).toBeVisible();
        await expect(winner).toContainText('Horse 2');
    });

    test('the /monitor/data endpoint returns the bare live fragment', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        const res = await page.request.get(`${BASE_URL}/wfbo/live-racing/monitor/data?event=${eventId}`);
        expect(res.status()).toBe(200);
        const body = await res.text();
        expect(body).toContain('lr-monitor-kpis');
        expect(body).toContain('lr-monitor-race-1');
        // The fragment must not carry the shell chrome (header / fullscreen button / video).
        expect(body).not.toContain('lr-monitor-fullscreen');
        expect(body).not.toContain('lr-monitor-video');
    });

    test('shows the idle screen for an unknown event id', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await openMonitor(page, '?event=99999999');

        await expect(page.locator('[data-testid="lr-monitor-idle"]')).toBeVisible();
        await expect(page.locator('[data-testid="lr-monitor-idle"]')).toContainText('not found');
    });

    test('is ROLE_ADMIN only — finance and operations are denied', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        expect((await page.request.get(`${BASE_URL}/wfbo/live-racing/monitor`)).status()).toBe(403);

        await loginAs(page, OPERATIONS.mobile, OPERATIONS.password);
        expect((await page.request.get(`${BASE_URL}/wfbo/live-racing/monitor`)).status()).toBe(403);
    });

    test('an event with a video URL embeds the player with its native controls shown', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);

        const arena = await createArena(page, 'LRMonitorVideo', { win: '10' });
        const { eventId: videoEventId } = await createEventShowUrl(page, arena);

        await page.goto(`${BASE_URL}/wfbo/events/${videoEventId}/edit`);
        await page.fill('[data-testid="input-event-video-url"]', 'https://www.youtube.com/watch?v=dQw4w9WgXcQ');
        await page.click('[data-testid="btn-submit"]');
        await expect(page).toHaveURL(`${BASE_URL}/wfbo/events`);

        await openMonitor(page, `?event=${videoEventId}`);
        const iframe = page.locator('[data-testid="lr-monitor-video"]');
        await expect(iframe).toBeVisible();
        expect(await iframe.getAttribute('src')).toContain('controls=1');
    });

    test('Active Players Today counts a player who placed a bet on the event', async ({ page }) => {
        // Live Racing defaults to disabled — every other spec that calls
        // placeWizardBet() enables it first; this one was missing that, which
        // sent the player wizard navigation back to the homepage instead.
        await ensureLiveRacingEnabled(page);

        const arena = await createArena(page, 'LRMonitorActive', { win: '10' });
        const { eventUrl, eventId: activeEventId } = await createEventShowUrl(page, arena);
        const race = await addRace(page, eventUrl, (Date.now() % 900) + 1);
        const horseIds = await addHorses(page, race.raceUrl, 2);
        await offerSingleBetTypes(page, eventUrl, [{ key: 'win', raceId: race.raceId }]);
        await publishEvent(page, eventUrl);

        const { mobile, password } = await registerAndLogin(page);

        await loginAs(page, AUDITOR.mobile, AUDITOR.password);
        await page.goto(`${BASE_URL}/wfbo/users?search=${encodeURIComponent(mobile)}`);
        const userRow = page.locator('[data-testid^="user-row-"]').first();
        await expect(userRow).toBeVisible();
        const userId = (await userRow.getAttribute('data-testid'))?.replace('user-row-', '');
        await page.goto(`${BASE_URL}/wfbo/users/${userId}/transactions`);
        await page.fill('[data-testid="adj-amount"]', '50');
        await page.fill('[data-testid="adj-reason"]', 'Acceptance test funding for LR bet');
        await page.click('[data-testid="adj-submit"]');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        await loginAs(page, mobile, password);
        await placeWizardBet(page, {
            eventId: activeEventId, raceId: race.raceId, cardLabel: 'Win',
            positions: { 1: [horseIds[0]] }, amount: '10',
        });

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await openMonitor(page, `?event=${activeEventId}`);
        const kpi = page.locator('[data-testid="lr-monitor-kpi"]');
        await expect(kpi.filter({ hasText: 'Active Players Today' })).toContainText('1');
        // Bet is still pending (race not settled) — win rate stays at the zero-state.
        await expect(kpi.filter({ hasText: 'Win Rate Today' })).toContainText('0%');

        // Recent Bets (bottom-right) shows the bet just placed.
        const tickerRow = page.locator('[data-testid="lr-monitor-ticker-row"]').first();
        await expect(tickerRow).toBeVisible();
        await expect(tickerRow).toContainText('Test');
        await expect(tickerRow).toContainText('10.00');

        // Top Earners (top-right, above Recent Bets) renders — no one has won
        // on this fresh event yet, so it's the empty state.
        await expect(page.locator('[data-testid="lr-monitor-earners"]')).toContainText('No winners yet');
    });
});
