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();
}

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

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

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

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

        test('a required dividend blocks the result from settling the race until it is provided', 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, 'win', raceId);
            await closeBetting(page, raceUrl);

            const winId = await dividendRowId(page, 'Win');
            await page.selectOption('[data-testid="select-finisher-1"]', horse1);
            await page.selectOption('[data-testid="select-finisher-2"]', horse2);
            // Dividend deliberately left blank.

            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: dividend is required.');
            await expect(page.locator('[data-testid="flash-success"]')).toHaveCount(0);
            await expect(page.locator('[data-testid="race-status"]')).not.toHaveText('Settled');

            await page.fill(`[data-testid="input-dividend-${winId}"]`, '125.50');
            await page.selectOption('[data-testid="select-finisher-1"]', horse1);
            await page.selectOption('[data-testid="select-finisher-2"]', horse2);
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
            await expect(page.locator('[data-testid="race-status"]')).toHaveText('Settled');
            await expect(page.locator('[data-testid="dividends-summary"]')).toContainText('125.50');
        });

        test('the Dividend field only shows Dead Heat A/B, labeled by horse, once a qualifying dead heat is marked', 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() ?? '';

            // Win is affected by a tie for 1st (PICK_COUNT win=1).
            await offerSingleBetType(page, eventUrl, 'win', raceId);
            await closeBetting(page, raceUrl);

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

            // Before any dead heat is marked, only the plain Dividend field shows.
            await expect(page.locator(`[data-testid="dividend-field-${winId}"]`)).toBeVisible();
            await expect(page.locator(`[data-testid="dead-heat-a-field-${winId}"]`)).toBeHidden();
            await expect(page.locator(`[data-testid="dead-heat-b-field-${winId}"]`)).toBeHidden();

            await page.selectOption('[data-testid="select-finisher-1"]', horse1);
            await page.selectOption('[data-testid="select-finisher-2"]', horse2);
            await page.check('[data-testid="checkbox-dead-heat-1"]');

            // Marking the tie swaps which fields show, and labels each by the
            // actual tied horse instead of the opaque "A"/"B".
            await expect(page.locator(`[data-testid="dividend-field-${winId}"]`)).toBeHidden();
            await expect(page.locator(`[data-testid="dead-heat-a-field-${winId}"]`)).toBeVisible();
            await expect(page.locator(`[data-testid="dead-heat-b-field-${winId}"]`)).toBeVisible();
            await expect(page.locator(`[data-testid="dead-heat-a-label-${winId}"]`)).toContainText('Horse #1');
            await expect(page.locator(`[data-testid="dead-heat-b-label-${winId}"]`)).toContainText('Horse #2');

            await page.fill(`[data-testid="input-dead-heat-a-${winId}"]`, '90.00');
            await page.fill(`[data-testid="input-dead-heat-b-${winId}"]`, '60.00');
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
            await expect(page.locator('[data-testid="dividends-summary"]')).toContainText('Dead Heat 90.00 / 60.00');
        });

        test('the server still rejects a plain dividend submitted for a dead-heat-affected bet type, even forced past the now-hidden field', 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, 'win', raceId);
            await closeBetting(page, raceUrl);

            const winId = await dividendRowId(page, 'Win');
            await page.selectOption('[data-testid="select-finisher-1"]', horse1);
            await page.selectOption('[data-testid="select-finisher-2"]', horse2);
            await page.check('[data-testid="checkbox-dead-heat-1"]');

            // The UI now hides this field once a qualifying dead heat is
            // marked - force past that to confirm the server still rejects
            // it independently of the client, not just relying on the UI.
            await page.fill(`[data-testid="input-dividend-${winId}"]`, '100.00', { force: true });

            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: dead heat dividends are required.');
            await expect(page.locator('[data-testid="flash-success"]')).toHaveCount(0);
        });

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

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

            const forecastId = await dividendRowId(page, 'Forecast');
            await page.selectOption('[data-testid="select-finisher-1"]', horse1);
            await page.check(`[data-testid="checkbox-refund-${forecastId}"]`);
            // Dividend deliberately left blank - refund should waive the requirement.

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

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

        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 dividendRowId(page, 'Odd Even');
            await expect(page.locator(`[data-testid="checkbox-refund-${oddEvenId}"]`)).toHaveCount(0);
        });

        test("a multi-race bet type's dividend is only required on the race that is 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);
            const horse1b = 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 dividend row, and the
            // result can settle without one.
            await closeBetting(page, race1Url);
            await expect(page.locator('[data-testid^="dividend-row-"]').filter({ hasText: 'Daily Double' })).toHaveCount(0);

            await page.selectOption('[data-testid="select-finisher-1"]', horse1a);
            await page.selectOption('[data-testid="select-finisher-2"]', horse1b);
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');
            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
            await expect(page.locator('[data-testid="race-status"]')).toHaveText('Settled');

            // Race 2 is the last leg - Daily Double's whole-combo dividend is
            // required here.
            await closeBetting(page, race2Url);
            const comboId = await dividendRowId(page, 'Daily Double');
            await expect(page.locator(`[data-testid="dividend-row-${comboId}"]`)).toContainText('whole combo');

            await page.selectOption('[data-testid="select-finisher-1"]', horse2a);
            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: dividend is required.');

            await page.selectOption('[data-testid="select-finisher-1"]', horse2a);
            await page.fill(`[data-testid="input-dividend-${comboId}"]`, '300.00');
            page.once('dialog', dialog => dialog.accept());
            await page.click('[data-testid="btn-save-result"]');

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

    });

});
