import { test, expect, Page } from '@playwright/test';
import { BASE_URL, ADMIN, loginAs, uniqueFutureDate } from '../helpers';

// Event lists are sorted by event date, not creation order, and this DB is shared
// (un-isolated) across spec files in the same test run, so the row we just created
// must be located by its unique date rather than by list position (.last()/.first()).
async function createEventShowUrl(page: Page): Promise<string> {
    const dateStr = uniqueFutureDate();
    await page.goto(`${BASE_URL}/wfbo/events/new`);
    await page.selectOption('[data-testid="select-event-management"]', { index: 1 });
    await page.fill('[data-testid="input-event-date"]', dateStr);
    await page.click('[data-testid="btn-submit"]');
    await expect(page).toHaveURL(`${BASE_URL}/wfbo/events`);

    const row = page.locator('[data-testid^="event-row-"]').filter({ hasText: dateStr });
    const eventHref = await row.locator('[data-testid^="btn-view-"]').getAttribute('href');
    return eventHref ?? '';
}

// Races are listed by raceNumber ASC, not creation order, so ".last()" only
// finds the just-created race when there's exactly one race in the event -
// with two+ races, a smaller race number sorts before an earlier-created
// larger one. Take an explicit race number and look up its row by that text
// instead, matching the uniqueFutureDate()-based event lookup pattern.
async function addRace(page: Page, eventUrl: string, raceNumber: number): Promise<string> {
    await page.goto(`${BASE_URL}${eventUrl}`);
    await page.click('[data-testid="btn-new-race"]');
    await page.fill('[data-testid="input-race-number"]', String(raceNumber));
    await page.click('[data-testid="btn-submit"]');

    const row = page.locator('[data-testid^="race-row-"]').filter({ hasText: String(raceNumber) });
    const raceHref = await row.locator('[data-testid^="btn-view-race-"]').getAttribute('href');
    return raceHref ?? '';
}

async function addHorse(page: Page, raceUrl: string, horseNumber: number): Promise<string> {
    await page.goto(`${BASE_URL}${raceUrl}`);
    await page.click('[data-testid="btn-new-horse"]');
    await page.fill('[data-testid="input-horse-number"]', String(horseNumber));
    await page.fill('[data-testid="input-horse-name"]', `Horse ${horseNumber}`);
    await page.fill('[data-testid="input-horse-jockey"]', `Jockey ${horseNumber}`);
    await page.click('[data-testid="btn-submit"]');

    const row = page.locator('[data-testid^="horse-row-"]').last();
    const horseId = (await row.getAttribute('data-testid'))?.replace('horse-row-', '');
    return horseId ?? '';
}

async function closeBetting(page: Page, raceUrl: string): Promise<void> {
    await page.goto(`${BASE_URL}${raceUrl}`);
    page.once('dialog', dialog => dialog.accept());
    await page.click('[data-testid="btn-close-betting"]');
    await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
}

async function offerSingleBetType(page: Page, eventUrl: string, key: string, raceId: string): Promise<void> {
    await page.goto(`${BASE_URL}${eventUrl}?tab=bet-types`);
    await page.check(`[data-testid="checkbox-leg-${key}-${raceId}"]`);
    await page.click('[data-testid="btn-save-bet-types"]');
    await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
}

async function offerMultiBetType(page: Page, eventUrl: string, key: string, raceIds: string[]): Promise<void> {
    await page.goto(`${BASE_URL}${eventUrl}?tab=bet-types`);
    await page.click(`[data-testid="btn-add-combo-${key}"]`);
    for (const raceId of raceIds) {
        await page.check(`[data-testid="checkbox-combo-${key}-new-0-${raceId}"]`);
    }
    await page.click('[data-testid="btn-save-bet-types"]');
    await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
}

// Combination-entry blocks are keyed by EventBetType id, which the test doesn't
// know ahead of time - locate the block by its visible label text and read the
// id back off its `data-combo-bettype` attribute.
async function comboBlockId(page: Page, label: string): Promise<string> {
    const block = page.locator('[data-testid^="combo-bettype-"]').filter({ hasText: label });
    const testId = await block.getAttribute('data-testid');
    return testId?.replace('combo-bettype-', '') ?? '';
}

// Fills combination row `rowIdx` of a bet type's block: one horse per slot select
// (in position/leg order) then the row's dividend input.
async function fillCombo(page: Page, betTypeId: string, rowIdx: number, horseIds: string[], dividend: string): Promise<void> {
    const row     = page.locator(`[data-testid="combo-bettype-${betTypeId}"] [data-combo-row]`).nth(rowIdx);
    const selects = row.locator('select');
    for (let s = 0; s < horseIds.length; s++) {
        await selects.nth(s).selectOption(horseIds[s]);
    }
    await row.locator('input[type="number"]').fill(dividend);
}

test.describe('WFBO Event Race Dividends', () => {

    test.describe('dividends as admin', () => {

        test.beforeEach(async ({ page }) => {
            await loginAs(page, ADMIN.mobile, ADMIN.password);
        });

        test('at least one winning combination is required before a result can be saved', async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const raceUrl = await addRace(page, eventUrl, 1);
            const horse1 = await addHorse(page, raceUrl, 1);
            await addHorse(page, raceUrl, 2);
            const raceId = raceUrl.split('/').pop() ?? '';

            await offerSingleBetType(page, eventUrl, 'win', raceId);
            await closeBetting(page, raceUrl);

            const winId = await comboBlockId(page, 'Win');

            // No combination entered - save is rejected.
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-error"]').first()).toContainText('Win: enter at least one winning combination.');
            await expect(page.locator('[data-testid="flash-success"]')).toHaveCount(0);

            await fillCombo(page, winId, 0, [horse1], '125.50');
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-success"]')).toContainText('Result saved');
            // Once saved the result is view-only - the entry form is gone and the
            // read-only summary shows the combination, pending settlement.
            await expect(page.locator('[data-testid="btn-save-result"]')).toHaveCount(0);
            await expect(page.locator('[data-testid="result-pending-settlement"]')).toBeVisible();
            await expect(page.locator('[data-testid="dividends-summary"]')).toContainText('125.50');
        });

        test('two winning combinations for the same bet type cover a dead heat', async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const raceUrl = await addRace(page, eventUrl, 1);
            const horse1 = await addHorse(page, raceUrl, 1);
            const horse2 = await addHorse(page, raceUrl, 2);
            const raceId = raceUrl.split('/').pop() ?? '';

            await offerSingleBetType(page, eventUrl, 'forecast', raceId);
            await closeBetting(page, raceUrl);

            const forecastId = await comboBlockId(page, 'Forecast');
            await page.click(`[data-testid="btn-add-combo-${forecastId}"]`);
            await fillCombo(page, forecastId, 0, [horse1, horse2], '40.00');
            await fillCombo(page, forecastId, 1, [horse2, horse1], '55.00');

            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-success"]')).toContainText('Result saved');
            // Both combinations appear in the read-only summary.
            const summary = page.locator('[data-testid="dividends-summary"]');
            await expect(summary).toContainText('40.00');
            await expect(summary).toContainText('55.00');
        });

        test('refund waives the combination requirement for a single-race bet type', async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const raceUrl = await addRace(page, eventUrl, 1);
            await addHorse(page, raceUrl, 1);
            const raceId = raceUrl.split('/').pop() ?? '';

            await offerSingleBetType(page, eventUrl, 'forecast', raceId);
            await closeBetting(page, raceUrl);

            const forecastId = await comboBlockId(page, 'Forecast');
            await page.check(`[data-testid="checkbox-refund-${forecastId}"]`);
            // No combination entered - refund waives the requirement.

            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-success"]')).toContainText('Result saved');
            await expect(page.locator('[data-testid="dividends-summary"]')).toContainText('Refunded');
        });

        test('scratched horses stay listed in the combination dropdowns, marked "[s]"', async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const raceUrl = await addRace(page, eventUrl, 1);
            await addHorse(page, raceUrl, 1);
            const horse2 = await addHorse(page, raceUrl, 2);
            const horse3 = await addHorse(page, raceUrl, 3);
            const raceId = raceUrl.split('/').pop() ?? '';

            // horse2 scratched while betting is still open.
            await page.goto(`${BASE_URL}${raceUrl}`);
            await page.check(`[data-testid="checkbox-scratch-${horse2}"]`);
            await page.click('[data-testid="btn-save-scratches"]');
            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

            await offerSingleBetType(page, eventUrl, 'win', raceId);
            await closeBetting(page, raceUrl);

            const winId = await comboBlockId(page, 'Win');
            const slot = `[data-testid="select-combo-${winId}-0-0"]`;
            // The before-close scratch is still listed, tagged "[s]", still selectable.
            await expect(page.locator(`${slot} option[value="${horse2}"]`)).toHaveText(/\[s\]/);
            await expect(page.locator(`${slot} option[value="${horse2}"]`)).toBeEnabled();
            // Ticking an after-close scratch tags that option too.
            await expect(page.locator(`${slot} option[value="${horse3}"]`)).not.toHaveText(/\[s\]/);
            await page.check(`[data-testid="checkbox-result-scratch-${horse3}"]`);
            await expect(page.locator(`${slot} option[value="${horse3}"]`)).toHaveText(/\[s\]/);
        });

        test('picking a finish position syncs it across the ordered bet types', async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const raceUrl = await addRace(page, eventUrl, 1);
            const h1 = await addHorse(page, raceUrl, 1);
            const h2 = await addHorse(page, raceUrl, 2);
            await addHorse(page, raceUrl, 3);
            const raceId = raceUrl.split('/').pop() ?? '';

            await offerSingleBetType(page, eventUrl, 'win', raceId);
            await offerSingleBetType(page, eventUrl, 'forecast', raceId);
            await offerSingleBetType(page, eventUrl, 'trifecta', raceId);
            await closeBetting(page, raceUrl);

            const winId = await comboBlockId(page, 'Win');
            const forecastId = await comboBlockId(page, 'Forecast');
            const trifectaId = await comboBlockId(page, 'Trifecta');

            // Position 1 picked in Win propagates to Forecast + Trifecta.
            await page.selectOption(`[data-testid="select-combo-${winId}-0-0"]`, h1);
            await expect(page.locator(`[data-testid="select-combo-${forecastId}-0-0"]`)).toHaveValue(h1);
            await expect(page.locator(`[data-testid="select-combo-${trifectaId}-0-0"]`)).toHaveValue(h1);

            // Position 2 picked in Trifecta propagates to Forecast (Win has no pos 2).
            await page.selectOption(`[data-testid="select-combo-${trifectaId}-0-1"]`, h2);
            await expect(page.locator(`[data-testid="select-combo-${forecastId}-0-1"]`)).toHaveValue(h2);
        });

        test('refund is rejected for Odd Even', async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const raceUrl = await addRace(page, eventUrl, 1);
            await addHorse(page, raceUrl, 1);
            const raceId = raceUrl.split('/').pop() ?? '';

            await offerSingleBetType(page, eventUrl, 'odd_even', raceId);
            await closeBetting(page, raceUrl);

            // Odd Even never renders a refund checkbox - confirm that directly.
            const oddEvenId = await comboBlockId(page, 'Odd Even');
            await expect(page.locator(`[data-testid="checkbox-refund-${oddEvenId}"]`)).toHaveCount(0);
        });

        test("a multi-race bet type's combination is only entered on its combo's last leg", async ({ page }) => {
            const eventUrl = await createEventShowUrl(page);
            const race1Url = await addRace(page, eventUrl, 1);
            const horse1a = await addHorse(page, race1Url, 1);
            await addHorse(page, race1Url, 2);
            const race2Url = await addRace(page, eventUrl, 2);
            const horse2a = await addHorse(page, race2Url, 1);
            const race1Id = race1Url.split('/').pop() ?? '';
            const race2Id = race2Url.split('/').pop() ?? '';

            await offerMultiBetType(page, eventUrl, 'daily_double', [race1Id, race2Id]);

            // Race 1 is not the last leg - no Daily Double combination block, and
            // the result can settle without one.
            await closeBetting(page, race1Url);
            await expect(page.locator('[data-testid^="combo-bettype-"]').filter({ hasText: 'Daily Double' })).toHaveCount(0);

            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');
            await expect(page.locator('[data-testid="flash-success"]')).toContainText('Result saved');

            // Race 2 is the last leg - Daily Double's whole-combo dividend is
            // entered here, one horse per leg.
            await closeBetting(page, race2Url);
            const comboId = await comboBlockId(page, 'Daily Double');

            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');
            await expect(page.locator('[data-testid="flash-error"]').first()).toContainText('Daily Double: enter at least one winning combination.');

            await fillCombo(page, comboId, 0, [horse1a, horse2a], '300.00');
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-success"]')).toContainText('Result saved');
            await expect(page.locator('[data-testid="btn-save-result"]')).toHaveCount(0);
            await expect(page.locator('[data-testid="dividends-summary"]')).toContainText('300.00');
        });

    });

});
