import { test, expect, type Page } from '@playwright/test';
import {
    BASE_URL,
    ADMIN,
    AUDITOR,
    FINANCE,
    OPERATIONS,
    loginAs,
    registerAndLogin,
    adjustWalletAsAuditor,
    ensureMinesEnabled,
} from '../helpers';

test.describe('WFBO Dashboard', () => {

    test.beforeEach(async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo`);
    });

    test('dashboard loads with total-users stat card', async ({ page }) => {
        await expect(page.locator('[data-testid="stat-total-users"]')).toBeVisible();
    });

    test('dashboard shows pending deposits stat card', async ({ page }) => {
        await expect(page.locator('[data-testid="stat-pending-deposits"]')).toBeVisible();
    });

    test('dashboard shows pending withdrawals stat card', async ({ page }) => {
        await expect(page.locator('[data-testid="stat-pending-withdrawals"]')).toBeVisible();
    });

    test('period filter buttons are present', async ({ page }) => {
        const filters = page.locator('[data-testid^="period-"]');
        await expect(filters.first()).toBeVisible();
        const count = await filters.count();
        // today, yesterday, this_week, last_week, this_month, last_month, this_year, last_year
        expect(count).toBeGreaterThanOrEqual(8);
    });

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

    test('clicking last_year period filter reloads and stat cards remain visible', async ({ page }) => {
        await page.click('[data-testid="period-last_year"]');
        await page.waitForLoadState('networkidle');
        await expect(page.locator('[data-testid="stat-total-users"]')).toBeVisible();
        await expect(page.locator('[data-testid="stat-gameCount"]')).toBeVisible();
    });

    test('clicking this_month period filter reloads and stat cards remain visible', async ({ page }) => {
        await page.click('[data-testid="period-this_month"]');
        await page.waitForLoadState('networkidle');
        await expect(page.locator('[data-testid="stat-total-users"]')).toBeVisible();
        await expect(page.locator('[data-testid="stat-gameCount"]')).toBeVisible();
    });

    for (const week of ['this_week', 'last_week']) {
        test(`clicking ${week} period filter reloads, marks it active, stat cards remain visible`, async ({ page }) => {
            await page.click(`[data-testid="period-${week}"]`);
            await page.waitForLoadState('networkidle');
            expect(page.url()).toContain(`period=${week}`);
            await expect(page.locator(`[data-testid="period-${week}"]`)).toHaveClass(/is-active/);
            await expect(page.locator('[data-testid="stat-total-users"]')).toBeVisible();
            await expect(page.locator('[data-testid="stat-gameCount"]')).toBeVisible();
        });
    }

    test('game count stat card is visible', async ({ page }) => {
        await expect(page.locator('[data-testid="stat-gameCount"]')).toBeVisible();
    });

    test('deposit requests split-stat card is visible', async ({ page }) => {
        await expect(page.locator('[data-testid="stat-deposit-requests"]')).toBeVisible();
    });

    test('withdrawal requests split-stat card is visible', async ({ page }) => {
        await expect(page.locator('[data-testid="stat-withdrawal-requests"]')).toBeVisible();
    });

    test('FINANCE user can access the dashboard', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        // FINANCE sees pending deposits/withdrawals, not total-users (ROLE_OPERATIONS only)
        await expect(page.locator('[data-testid="stat-pending-deposits"]')).toBeVisible();
    });

    test('OPERATIONS user can access the dashboard', async ({ page }) => {
        await loginAs(page, OPERATIONS.mobile, OPERATIONS.password);
        await page.goto(`${BASE_URL}/wfbo`);
        await expect(page.locator('[data-testid="stat-total-users"]')).toBeVisible();
    });

    test('plain USER is blocked from /wfbo (no WFBO sidebar visible)', async ({ page }) => {
        // Use a fresh registration so this user has no roles that could be promoted by other tests
        await page.context().clearCookies();
        await registerAndLogin(page);
        await page.goto(`${BASE_URL}/wfbo`);
        // Whether Symfony redirects or returns 403, the WFBO sidebar must not appear
        await expect(page.locator('[data-testid="wfbo-sidebar"]')).not.toBeVisible();
    });

    test('financial chart card is visible for FINANCE user', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        await expect(page.locator('[data-testid="wfbo-financial-chart"]')).toBeVisible();
    });

    test('net revenue trend chart card is visible for FINANCE user', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        await expect(page.locator('[data-testid="wfbo-revenue-trend-chart"]')).toBeVisible();
        await expect(page.locator('[data-testid="wfbo-revenue-trend-chart"] canvas')).toBeVisible();
    });

    test('revenue-chart API returns 72 cumulative buckets', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        const response = await page.request.get(`${BASE_URL}/wfbo/revenue-chart`);
        expect(response.status()).toBe(200);
        const data = await response.json();
        expect(Array.isArray(data.labels)).toBe(true);
        expect(data.labels).toHaveLength(72);
        expect(data.cumulative).toHaveLength(72);
        expect(data).toHaveProperty('asOf');
    });

    test('dashboard renders for period=this_year', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        const response = await page.goto(`${BASE_URL}/wfbo?period=this_year`);
        expect(response?.status()).toBe(200);
        await expect(page.locator('[data-testid="wfbo-revenue-trend-chart"]')).toBeVisible();
    });

    test('pending-status API returns JSON with required fields', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        const response = await page.request.get(`${BASE_URL}/wfbo/pending-status`);
        expect(response.status()).toBe(200);
        const data = await response.json();
        expect(data).toHaveProperty('pendingDeposits');
        expect(data).toHaveProperty('pendingWithdrawals');
        expect(data).toHaveProperty('cashOnHand');
        expect(data).toHaveProperty('totalPlayerBalance');
        expect(typeof data.pendingDeposits).toBe('number');
        expect(typeof data.pendingWithdrawals).toBe('number');
        expect(typeof data.cashOnHand).toBe('number');
    });

    test('cash on hand card is auditor-only and scoped to today', async ({ page }) => {
        // Visible to an auditor…
        await loginAs(page, AUDITOR.mobile, AUDITOR.password);
        await page.goto(`${BASE_URL}/wfbo`);
        await expect(page.locator('[data-testid="stat-cash-on-hand"]')).toBeVisible();
        await expect(page.locator('[data-testid="stat-cash-on-hand"]')).toContainText('Today');

        // …hidden from a plain finance user and a plain admin.
        for (const staff of [FINANCE, ADMIN]) {
            await loginAs(page, staff.mobile, staff.password);
            await page.goto(`${BASE_URL}/wfbo`);
            await expect(page.locator('[data-testid="stat-cash-on-hand"]')).toHaveCount(0);
        }
    });

    test('overview row counts master agents, agents and sub agents', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo`);
        for (const id of ['stat-total-master-agents', 'stat-total-agents', 'stat-total-sub-agents']) {
            const tile = page.locator(`[data-testid="${id}"]`);
            await expect(tile).toBeVisible();
            await expect(tile.locator('.stat-value')).toHaveText(/^\d+$/);
        }
    });

    test('wallet balances tile breaks down by role', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        const tile = page.locator('[data-testid="stat-total-player-balance"]');
        await expect(tile).toBeVisible();
        for (const id of [
            'stat-player-balance-players',
            'stat-player-balance-sub-agents',
            'stat-player-balance-agents',
            'stat-player-balance-master-agents',
        ]) {
            await expect(tile.locator(`[data-testid="${id}"]`)).toContainText('₱');
        }
    });

    test('dashboard-chart API returns JSON with labels and data arrays', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        const response = await page.request.get(`${BASE_URL}/wfbo/dashboard-chart?period=today`);
        expect(response.status()).toBe(200);
        const data = await response.json();
        expect(data).toHaveProperty('labels');
        expect(data).toHaveProperty('deposits');
        expect(data).toHaveProperty('withdrawals');
        expect(Array.isArray(data.labels)).toBe(true);
        expect(Array.isArray(data.deposits)).toBe(true);
        expect(Array.isArray(data.withdrawals)).toBe(true);
        expect(data.labels.length).toBe(data.deposits.length);
        expect(data.labels.length).toBe(data.withdrawals.length);
    });

    test('pending deposits stat card has live-update data target attribute', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        await expect(page.locator('[data-wfbo-pending-target="pendingDepositsValue"]')).toBeAttached();
        await expect(page.locator('[data-wfbo-pending-target="pendingWithdrawalsValue"]')).toBeAttached();
    });

    test('finance stat cards include a Mines (MN) per-game breakdown', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        await expect(page.locator('[data-testid="stat-betAmount"]')).toContainText('MN:');
        await expect(page.locator('[data-testid="stat-commissions"]')).toContainText('MN:');
        await expect(page.locator('[data-testid="stat-revenue"]')).toContainText('MN:');
        await expect(page.locator('[data-testid="stat-winAmount"]')).toContainText('MN:');
        await expect(page.locator('[data-testid="stat-winLooseRate"]')).toContainText('MN:');
    });

    test('per-game breakdown renders as badges, not divider-separated text', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);
        const betAmountBadges = page.locator('[data-testid="stat-betAmount"] .stat-game-badge');
        await expect(betAmountBadges).toHaveCount(9);
        await expect(betAmountBadges.first()).toContainText('VK:');
        await expect(page.locator('[data-testid="stat-betAmount"] .stat-game-divider')).toHaveCount(0);
    });

    test('Net Revenue headline equals the sum of its per-game breakdown badges', async ({ page }) => {
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo`);

        const parsePeso = (s: string): number =>
            parseFloat((s.match(/-?₱?\s*(-?[\d,]+\.\d{2})/)?.[1] ?? '0').replace(/,/g, ''));

        const headline = parsePeso(
            (await page.locator('[data-testid="stat-revenue"] .stat-value--lg').textContent()) ?? '',
        );

        const badgeTexts = await page
            .locator('[data-testid="stat-revenue"] .stat-game-badge')
            .allTextContents();
        const badgeSum = badgeTexts.reduce((acc, t) => acc + parsePeso(t), 0);

        expect(Math.abs(headline - badgeSum)).toBeLessThan(0.01);
    });

});

test.describe('WFBO Dashboard — Active Players counts every game', () => {
    test.setTimeout(180_000);

    async function readActivePlayers(page: Page): Promise<number> {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo?period=today`);
        const txt = (await page.locator('[data-testid="stat-activePlayers-value"]').textContent()) ?? '';
        return parseInt(txt.trim(), 10) || 0;
    }

    test('a non-VK game bet makes the player count as active', async ({ page }) => {
        // Fresh player, funded. Mines stands in for "any game that isn't VK
        // race betting" - the old metric only counted race_bet rows. Starting a
        // round is enough: it deducts the stake and writes the mines_game row.
        const player = await registerAndLogin(page);
        await adjustWalletAsAuditor(page, player.mobile, '500.00', 'active-players test');
        await ensureMinesEnabled(page);

        const before = await readActivePlayers(page);

        await loginAs(page, player.mobile, player.password);
        await page.goto(`${BASE_URL}/play/mines`, { waitUntil: 'domcontentloaded' });
        await page.locator('[data-testid="mg-bet-chip"][data-amount="5"]').click();
        await page.locator('[data-testid="mg-tier-green"]').click();
        await page.click('[data-testid="btn-mg-start"]');
        await expect(page.locator('[data-testid="btn-mg-cashout"]')).toBeVisible();

        const after = await readActivePlayers(page);
        expect(after).toBeGreaterThanOrEqual(before + 1);
    });

    test('the tile shows active players as a % of the player population', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo?period=this_year`);

        const sub = page.locator('[data-testid="stat-activePlayers-percent"]');
        await expect(sub).toBeVisible();
        const text = ((await sub.textContent()) ?? '').replace(/\s+/g, ' ').trim();
        expect(text).toMatch(/^\d+(\.\d)?% of [\d,]+ players$/);

        const pct = parseFloat(text);
        expect(pct).toBeGreaterThanOrEqual(0);
        expect(pct).toBeLessThanOrEqual(100);
    });
});

test.describe('WFBO Dashboard — Players Logged In counts presence', () => {
    test.setTimeout(120_000);

    async function readLoggedIn(page: Page): Promise<number> {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo?period=today`);
        const txt = (await page.locator('[data-testid="stat-playersLoggedIn-value"]').textContent()) ?? '';
        return parseInt(txt.trim(), 10) || 0;
    }

    test('a player just browsing (no bet) is counted as logged in today', async ({ page }) => {
        const before = await readLoggedIn(page);

        // Fresh player logs in and loads a page - no bet, no game played.
        const player = await registerAndLogin(page);
        await page.goto(`${BASE_URL}/`, { waitUntil: 'domcontentloaded' });

        const after = await readLoggedIn(page);
        expect(after).toBeGreaterThanOrEqual(before + 1);
    });
});
