import { test, expect, Page } from '@playwright/test';
import {
    BASE_URL, ADMIN, AGENT, FINANCE, AUDITOR, SUPER_ADMIN, loginAs, registerAndLogin, ensureMinesEnabled, playMinesRound,
    ensureLiveRacingEnabled, createArena, createEventShowUrl, addRace, addHorses,
    offerSingleBetTypes, publishEvent, placeWizardBet,
} from '../helpers';

test.describe('WFBO User Transactions & Wallet Adjustment', () => {

    async function navigateToFirstUserTransactions(page: Page) {
        await page.goto(`${BASE_URL}/wfbo/users`);
        const userRow = page.locator('[data-testid^="user-row-"]').first();
        await expect(userRow).toBeVisible();
        const rowId = (await userRow.getAttribute('data-testid'))?.replace('user-row-', '');
        await page.goto(`${BASE_URL}/wfbo/users/${rowId}/transactions`);
        return rowId;
    }

    test.beforeEach(async ({ page }) => {
        // The seeded Auditor is ROLE_ADMIN + ROLE_AUDITOR — full WFBO nav plus
        // the only role that may submit System Adjustments.
        await loginAs(page, AUDITOR.mobile, AUDITOR.password);
    });

    test('navigating to user transactions shows user name', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="user-transactions-name"]')).toBeVisible();
    });

    test('user wallet balance is displayed', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="user-wallet-balance"]')).toBeVisible();
    });

    test('transactions table is visible on the user transactions page', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="user-transactions-table"]')).toBeVisible();
    });

    test('upline name in the Agents tree has a copy button', async ({ page }) => {
        // Fixture-seeded player referred directly by the Master Agent, so the
        // Agents tree always has exactly one upline node to click through to.
        await page.goto(`${BASE_URL}/wfbo/users?search=%2B10000000101`);
        const userRow = page.locator('[data-testid^="user-row-"]').first();
        if (await userRow.count() === 0) {
            test.skip();
            return;
        }
        const rowId = (await userRow.getAttribute('data-testid'))?.replace('user-row-', '');
        await page.goto(`${BASE_URL}/wfbo/users/${rowId}/transactions`);

        const copyBtn = page.locator('[data-testid^="btn-copy-upline-"]').first();
        if (await copyBtn.count() === 0) {
            test.skip();
            return;
        }
        await expect(copyBtn).toBeVisible();
    });

    test('System Adjustment form is visible to an auditor', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="wallet-adjustment-form"]')).toBeVisible();
    });

    test('a plain admin (no auditor role) cannot see or submit the System Adjustment form', async ({ page }) => {
        const rowId = await navigateToFirstUserTransactions(page); // as AUDITOR, to grab an id

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/users/${rowId}/transactions`);
        await expect(page.locator('[data-testid="wallet-adjustment-form"]')).toHaveCount(0);

        const res = await page.request.post(`${BASE_URL}/wfbo/users/${rowId}/wallet-adjustment`, {
            form: { amount: '10', reason: 'should be blocked' },
        });
        expect(res.status()).toBe(403);
    });

    test('submitting a positive wallet adjustment shows flash-success and updates balance', async ({ page }) => {
        await navigateToFirstUserTransactions(page);

        const balanceBefore = await page.locator('[data-testid="user-wallet-balance"]').textContent();
        const beforeNum = parseFloat(balanceBefore?.replace(/[^\d.]/g, '') ?? '0');

        await page.fill('[data-testid="adj-amount"]', '50');
        await page.fill('[data-testid="adj-reason"]', 'Acceptance test credit');
        await page.click('[data-testid="adj-submit"]');

        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        const balanceAfter = await page.locator('[data-testid="user-wallet-balance"]').textContent();
        const afterNum = parseFloat(balanceAfter?.replace(/[^\d.]/g, '') ?? '0');
        expect(afterNum).toBeGreaterThan(beforeNum);
    });

    test('submitting a negative wallet adjustment shows flash-success and updates balance', async ({ page }) => {
        await navigateToFirstUserTransactions(page);

        // Credit first to ensure sufficient balance for debit
        await page.fill('[data-testid="adj-amount"]', '100');
        await page.fill('[data-testid="adj-reason"]', 'Pre-debit credit');
        await page.click('[data-testid="adj-submit"]');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        const balanceBefore = await page.locator('[data-testid="user-wallet-balance"]').textContent();
        const beforeNum = parseFloat(balanceBefore?.replace(/[^\d.]/g, '') ?? '0');

        await page.fill('[data-testid="adj-amount"]', '-50');
        await page.fill('[data-testid="adj-reason"]', 'Acceptance test debit');
        await page.click('[data-testid="adj-submit"]');

        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        const balanceAfter = await page.locator('[data-testid="user-wallet-balance"]').textContent();
        const afterNum = parseFloat(balanceAfter?.replace(/[^\d.]/g, '') ?? '0');
        expect(afterNum).toBeLessThan(beforeNum);
    });

    test('submitting an adjustment that exceeds balance shows error', async ({ page }) => {
        await navigateToFirstUserTransactions(page);

        // Enter a huge negative adjustment
        await page.fill('[data-testid="adj-amount"]', '-9999999');
        await page.fill('[data-testid="adj-reason"]', 'Overdraft test');
        await page.click('[data-testid="adj-submit"]');

        await expect(page.locator('[data-testid="flash-error"]')).toBeVisible();
    });

    test('period filters include this_year and last_year', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="period-this_year"]')).toBeVisible();
        await expect(page.locator('[data-testid="period-last_year"]')).toBeVisible();
    });

    test('transactions refresh button is visible', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="btn-transactions-refresh"]')).toBeVisible();
    });

    test('adjustments tab shows the unified transactions table', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await page.click('[data-testid="tab-adjustments"]');
        await expect(page).toHaveURL(/type=adjustments/);
        await expect(page.locator('[data-testid="user-transactions-table"]')).toBeVisible();
    });

    test('back to users link navigates back to /wfbo/users', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await page.click('[data-testid="btn-back-to-users"]');
        await expect(page).toHaveURL(`${BASE_URL}/wfbo/users`);
    });

    test('players tab shows Last Seen / Last Bet columns, activity KPIs, and sorts by last seen', async ({ page }) => {
        // Seeded Master Agent + three referred players (fixtures): "Recent Referral"
        // seen 5m ago and bet today, "Earlier Referral" seen 3h ago, "Dormant
        // Referral" never seen.
        await page.goto(`${BASE_URL}/wfbo/users?search=${encodeURIComponent(AGENT.mobile)}`);
        const agentRow = page.locator('[data-testid^="user-row-"]').first();
        await expect(agentRow).toBeVisible();
        const agentId = (await agentRow.getAttribute('data-testid'))!.replace('user-row-', '');

        await page.goto(`${BASE_URL}/wfbo/users/${agentId}/transactions?type=players`);

        // New columns.
        const table = page.locator('[data-testid="user-players-table"]');
        await expect(table.locator('thead')).toContainText('Last Seen');
        await expect(table.locator('thead')).toContainText('Last Bet');

        // KPI cards (default period = today).
        await expect(page.locator('[data-testid="stat-players-logged-in"]')).toBeVisible();
        await expect(page.locator('[data-testid="stat-active-players"]')).toBeVisible();
        expect(parseInt((await page.locator('[data-testid="stat-players-logged-in"]').innerText()).trim(), 10)).toBeGreaterThanOrEqual(1);
        expect(parseInt((await page.locator('[data-testid="stat-active-players"]').innerText()).trim(), 10)).toBeGreaterThanOrEqual(1);

        // Sorted by last-seen desc: "Recent" before "Earlier" before "Dormant".
        const names = await table.locator('tbody tr td:nth-child(2)').allInnerTexts();
        const idxRecent  = names.findIndex(n => n.includes('Recent Referral'));
        const idxEarlier = names.findIndex(n => n.includes('Earlier Referral'));
        const idxDormant = names.findIndex(n => n.includes('Dormant Referral'));
        expect(idxRecent).toBeGreaterThanOrEqual(0);
        expect(idxRecent).toBeLessThan(idxEarlier);
        expect(idxEarlier).toBeLessThan(idxDormant);

        // "Recent" has a real Last Bet value; "Dormant" shows the em-dash.
        const recentRow  = table.locator('tbody tr', { hasText: 'Recent Referral' });
        const dormantRow = table.locator('tbody tr', { hasText: 'Dormant Referral' });
        await expect(recentRow.locator('td').nth(5)).not.toHaveText('—');
        await expect(dormantRow.locator('td').nth(4)).toHaveText('—'); // Last Seen
        await expect(dormantRow.locator('td').nth(5)).toHaveText('—'); // Last Bet
    });

    test('FINANCE user can view user transactions when navigated directly', async ({ page }) => {
        // Get a user ID from ADMIN context first
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        const rowId = await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="user-transactions-name"]')).toBeVisible();

        // Now verify FINANCE can also access the same page directly
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo/users/${rowId}/transactions`);
        await expect(page.locator('[data-testid="user-transactions-name"]')).toBeVisible();
    });

    test('Actions dropdown is visible on user transactions page for ADMIN', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await expect(page.locator('[data-testid="btn-actions"]')).toBeVisible();
    });

    test('Actions dropdown contains expected action items for ADMIN', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await page.click('[data-testid="btn-actions"]');
        // At minimum, reset password should always be visible for ADMIN
        await expect(page.locator('[data-testid="btn-reset-password"]')).toBeVisible();
    });

    test('Actions dropdown shows suspend/unsuspend based on user status', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await page.click('[data-testid="btn-actions"]');
        // Should see either suspend or unsuspend button (not both)
        const suspendBtn = page.locator('[data-testid="btn-suspend"]');
        const unsuspendBtn = page.locator('[data-testid="btn-unsuspend"]');
        const suspendVisible = await suspendBtn.isVisible().catch(() => false);
        const unsuspendVisible = await unsuspendBtn.isVisible().catch(() => false);
        expect(suspendVisible || unsuspendVisible).toBeTruthy();
    });

    test('Actions dropdown contains house win rate option', async ({ page }) => {
        await navigateToFirstUserTransactions(page);
        await page.click('[data-testid="btn-actions"]');
        await expect(page.locator('[data-testid="btn-house-win-rate"]')).toBeVisible();
    });

    test('bets tab shows a Mines row after the player completes a round', async ({ page }) => {
        await ensureMinesEnabled(page); // ends logged in as ADMIN

        const { mobile, password } = await registerAndLogin(page); // ends logged in as the fresh player

        // Credit the fresh player's wallet so they can afford the minimum Mines bet.
        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"]', '100');
        await page.fill('[data-testid="adj-reason"]', 'Acceptance test funding for Mines round');
        await page.click('[data-testid="adj-submit"]');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        await loginAs(page, mobile, password);
        const played = await playMinesRound(page);
        expect(played).toBeTruthy();

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/users/${userId}/transactions?type=bets`);
        await expect(page.locator('[data-testid^="bet-row-mines-"]').first()).toBeVisible();
        // The Detail column is rendered inside a badge pill.
        await expect(page.locator('[data-testid^="bet-row-mines-"] .badge-detail').first()).toBeVisible();
    });

    test('the bets tab exports a player\'s Live Racing bets for a chosen day as a PDF', async ({ page }) => {
        await ensureLiveRacingEnabled(page); // ends logged in as ADMIN

        const arenaName = await createArena(page, 'LRExport', { win: '10.00' });
        const { eventUrl, eventId } = await createEventShowUrl(page, arenaName);
        const { raceUrl, raceId }   = await addRace(page, eventUrl, (Date.now() % 900) + 1);
        const horseIds = await addHorses(page, raceUrl, 2);
        await offerSingleBetTypes(page, eventUrl, [{ key: 'win', 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"]', '100');
        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, raceId, cardLabel: 'Win', positions: { 1: [horseIds[0]] }, amount: '10',
        });

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/users/${userId}/transactions?type=bets`);

        const exportForm = page.locator('[data-testid="lr-bets-export-form"]');
        await expect(exportForm).toBeVisible();
        const today = new Date().toISOString().slice(0, 10);
        await page.fill('[data-testid="lr-bets-export-date"]', today);

        const downloadPromise = page.waitForEvent('download');
        await page.click('[data-testid="btn-export-lr-bets"]');
        const download = await downloadPromise;
        expect(download.suggestedFilename()).toMatch(/^live-racing-bets-.*-\d{8}\.pdf$/);
    });

});

test.describe('WFBO House Agent toggle', () => {

    async function gotoAgentTransactions(page: Page): Promise<string> {
        await page.goto(`${BASE_URL}/wfbo/users?search=${encodeURIComponent(AGENT.mobile)}`);
        const row = page.locator('[data-testid^="user-row-"]').first();
        await expect(row).toBeVisible();
        const id = (await row.getAttribute('data-testid'))!.replace('user-row-', '');
        await page.goto(`${BASE_URL}/wfbo/users/${id}/transactions`);
        return id;
    }

    test.afterEach(async ({ page }) => {
        // Leave the seeded Master Agent un-flagged for other specs.
        await loginAs(page, SUPER_ADMIN.mobile, SUPER_ADMIN.password);
        const id = await gotoAgentTransactions(page);
        if (await page.locator('[data-testid="badge-house-agent"]').count() > 0) {
            await page.click('[data-testid="btn-actions"]');
            await page.click('[data-testid="btn-disable-house-agent"]');
            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
        }
    });

    test('a plain admin cannot see the House Agent toggle or POST to the route', async ({ page }) => {
        await loginAs(page, SUPER_ADMIN.mobile, SUPER_ADMIN.password);
        const id = await gotoAgentTransactions(page); // grab id as super admin

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/users/${id}/transactions`);
        await page.click('[data-testid="btn-actions"]');
        await expect(page.locator('[data-testid="btn-enable-house-agent"]')).toHaveCount(0);

        const res = await page.request.post(`${BASE_URL}/wfbo/users/${id}/house-agent`, {
            form: { enabled: '1' },
        });
        expect(res.status()).toBe(403);
    });

    test('super admin can flag and unflag a master agent as a House Agent', async ({ page }) => {
        await loginAs(page, SUPER_ADMIN.mobile, SUPER_ADMIN.password);
        const id = await gotoAgentTransactions(page);

        await expect(page.locator('[data-testid="badge-house-agent"]')).toHaveCount(0);

        // Enable → redirects back to the users list with a flash.
        await page.click('[data-testid="btn-actions"]');
        await page.click('[data-testid="btn-enable-house-agent"]');
        await page.click('#confirmActionBtn');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
        await expect(page.locator(`[data-testid="badge-house-agent-${id}"]`)).toBeVisible();

        // Badge also shows on the transactions page.
        await page.goto(`${BASE_URL}/wfbo/users/${id}/transactions`);
        await expect(page.locator('[data-testid="badge-house-agent"]')).toBeVisible();

        // Disable.
        await page.click('[data-testid="btn-actions"]');
        await page.click('[data-testid="btn-disable-house-agent"]');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
        await expect(page.locator(`[data-testid="badge-house-agent-${id}"]`)).toHaveCount(0);
    });

    test('flagging a master agent removes its balance from the Wallet Balances tile entirely', async ({ page }) => {
        await loginAs(page, SUPER_ADMIN.mobile, SUPER_ADMIN.password);

        const readTile = async () => {
            await page.goto(`${BASE_URL}/wfbo`);
            const parseAmt = async (sel: string) =>
                parseFloat((await page.locator(sel).innerText()).replace(/[₱,\s]/g, ''));
            return {
                total: await parseAmt('[data-testid="stat-total-player-balance"] .stat-value'),
                masterAgents: await parseAmt('[data-testid="stat-player-balance-master-agents"] span:last-child'),
            };
        };

        const before = await readTile();

        await gotoAgentTransactions(page);
        await page.click('[data-testid="btn-actions"]');
        await page.click('[data-testid="btn-enable-house-agent"]');
        await page.click('#confirmActionBtn');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        const after = await readTile();

        // The seeded Master Agent's balance leaves both the itemised row…
        const rowDrop = before.masterAgents - after.masterAgents;
        expect(rowDrop).toBeGreaterThan(0);
        // …and the headline Total, by the same amount (house cash is not a liability).
        expect(before.total - after.total).toBeCloseTo(rowDrop, 2);
    });
});
