import { test, expect } from '@playwright/test';
import { BASE_URL, ADMIN, FINANCE, FINANCE2, OPERATIONS, USER, loginAs } from '../helpers';

test.describe('WFBO Deposit Management — Extended', () => {

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

    test('deposits page loads with table', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
    });

    test('shows Total Deposits and Total Withdrawals Today cards', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);

        const depositsCard = page.locator('[data-testid="stat-deposits-today"]');
        await expect(depositsCard).toBeVisible();
        await expect(depositsCard).toContainText('Total Deposits');
        await expect(depositsCard).toContainText('Today');
        await expect(depositsCard).toContainText('₱');
        const depositsPending = page.locator('[data-testid="stat-deposits-pending"]');
        await expect(depositsPending).toContainText('Pending');
        await expect(depositsPending).toContainText('₱');

        const withdrawalsCard = page.locator('[data-testid="stat-withdrawals-today"]');
        await expect(withdrawalsCard).toBeVisible();
        await expect(withdrawalsCard).toContainText('Total Withdrawals');
        await expect(withdrawalsCard).toContainText('Today');
        await expect(withdrawalsCard).toContainText('₱');
        const withdrawalsPending = page.locator('[data-testid="stat-withdrawals-pending"]');
        await expect(withdrawalsPending).toContainText('Pending');
        await expect(withdrawalsPending).toContainText('₱');
    });

    test('shows the Deposit Trend bar graph grouped daily, weekly or monthly', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);

        const trend = page.locator('[data-testid="deposit-trend"]');
        await expect(trend).toBeVisible();
        await expect(trend).toContainText('Deposit Trend');
        await expect(page.locator('[data-testid="deposit-trend-chart"]')).toBeVisible();
        await expect(page.locator('[data-testid="deposit-trend-range-daily"]')).toHaveClass(/is-active/);
        await expect(page.locator('[data-testid="deposit-trend-summary"]')).toContainText('last 30 days');
        await expect(page.locator('[data-testid="deposit-trend-change"]')).toBeVisible();

        await page.locator('[data-testid="deposit-trend-range-weekly"]').click();
        await expect(page).toHaveURL(/trend=weekly/);
        await expect(page.locator('[data-testid="deposit-trend-range-weekly"]')).toHaveClass(/is-active/);
        await expect(page.locator('[data-testid="deposit-trend-summary"]')).toContainText('last 12 weeks');
        await expect(page.locator('[data-testid="deposit-trend-chart"]')).toBeVisible();

        await page.locator('[data-testid="deposit-trend-range-monthly"]').click();
        await expect(page).toHaveURL(/trend=monthly/);
        await expect(page.locator('[data-testid="deposit-trend-range-monthly"]')).toHaveClass(/is-active/);
        await expect(page.locator('[data-testid="deposit-trend-summary"]')).toContainText('last 12 months');
        await expect(page.locator('[data-testid="deposit-trend-summary"]')).toContainText('per month');
        // Chart.js drew into the canvas (non-zero backing size).
        const width = await page.locator('[data-testid="deposit-trend-chart"]').evaluate((c: HTMLCanvasElement) => c.width);
        expect(width).toBeGreaterThan(0);
    });

    test('deposits table has Sent To column', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const header = page.locator('[data-testid="deposits-table"] th', { hasText: 'Sent To' });
        await expect(header).toBeVisible();
    });

    test('clicking a user in the deposits list opens their transactions page', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const userLink = page.locator('[data-testid^="link-deposit-user-"]').first();
        if (await userLink.count() === 0) {
            test.skip();
            return;
        }
        await userLink.click();
        await expect(page).toHaveURL(/\/wfbo\/users\/\d+\/transactions/);
    });

    test('status filter tabs are visible', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await expect(page.locator('[data-testid="deposit-status-filters"]')).toBeVisible();
    });

    test('clicking Approved filter reloads with filtered view', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const approvedFilter = page.locator('[data-testid="deposit-status-filters"]').locator('text=Approved');
        await approvedFilter.click();
        await page.waitForLoadState('networkidle');
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
        expect(page.url()).toContain('status=approved');
    });

    test('clicking Rejected filter reloads with filtered view', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const rejectedFilter = page.locator('[data-testid="deposit-status-filters"]').locator('text=Rejected');
        await rejectedFilter.click();
        await page.waitForLoadState('networkidle');
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
        expect(page.url()).toContain('status=rejected');
    });

    test('can approve a pending deposit if one exists', async ({ page }) => {
        // Seeded pending deposits have no payment method / assigned handler, so
        // only an admin (not a plain FINANCE user) can act on them.
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const approveBtn = page.locator('[data-testid^="btn-approve-"]').first();
        if (await approveBtn.count() === 0) {
            test.skip();
            return;
        }
        const id = (await approveBtn.getAttribute('data-testid'))?.replace('btn-approve-', '');
        page.once('dialog', dialog => dialog.accept());
        await approveBtn.click();
        await page.waitForLoadState('networkidle');
        if (await page.locator(`[data-testid="deposit-row-${id}"]`).count() > 0) {
            await page.goto(`${BASE_URL}/wfbo/deposits`);
        }
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).not.toBeAttached({ timeout: 10000 });
    });

    test('can reject a pending deposit with a review note', async ({ page }) => {
        // Seeded pending deposits have no payment method / assigned handler, so
        // only an admin (not a plain FINANCE user) can act on them.
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const rejectBtn = page.locator('[data-testid^="btn-reject-"]').first();
        if (await rejectBtn.count() === 0) {
            test.skip();
            return;
        }
        const id = (await rejectBtn.getAttribute('data-testid'))?.replace('btn-reject-', '');
        const noteInput = page.locator(`[data-testid="input-review-note-${id}"]`);
        if (await noteInput.count() > 0) {
            await noteInput.fill('Insufficient proof of payment');
        }
        page.once('dialog', dialog => dialog.accept());
        await rejectBtn.click();
        await page.waitForLoadState('networkidle');
        if (await page.locator(`[data-testid="deposit-row-${id}"]`).count() > 0) {
            await page.goto(`${BASE_URL}/wfbo/deposits`);
        }
        await expect(page.locator(`[data-testid="btn-reject-${id}"]`)).not.toBeAttached({ timeout: 10000 });
    });

    test('declining the confirmation leaves the deposit pending', async ({ page }) => {
        // Seeded pending deposits have no payment method / assigned handler, so
        // only an admin (not a plain FINANCE user) can act on them.
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const approveBtn = page.locator('[data-testid^="btn-approve-"]').first();
        if (await approveBtn.count() === 0) {
            test.skip();
            return;
        }
        const id = (await approveBtn.getAttribute('data-testid'))?.replace('btn-approve-', '');

        let requestFired = false;
        await page.route(`**/wfbo/deposits/${id}/action/approve`, (route) => {
            requestFired = true;
            return route.continue();
        });

        page.once('dialog', dialog => dialog.dismiss());
        await approveBtn.click();
        await page.waitForTimeout(300);

        expect(requestFired).toBe(false);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).toBeAttached();
        await expect(page.locator(`[data-testid="deposit-row-${id}"]`)).not.toHaveClass(/is-processing/);
    });

    test('shows a processing state on the row while the approval request is in flight', async ({ page }) => {
        // Seeded pending deposits have no payment method / assigned handler, so
        // only an admin (not a plain FINANCE user) can act on them.
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const approveBtn = page.locator('[data-testid^="btn-approve-"]').first();
        if (await approveBtn.count() === 0) {
            test.skip();
            return;
        }
        const id = (await approveBtn.getAttribute('data-testid'))?.replace('btn-approve-', '');

        await page.route(`**/wfbo/deposits/${id}/action/approve`, async (route) => {
            await new Promise((resolve) => setTimeout(resolve, 800));
            await route.continue();
        });

        page.once('dialog', dialog => dialog.accept());
        await approveBtn.click();

        const row = page.locator(`[data-testid="deposit-row-${id}"]`);
        await expect(row).toHaveClass(/is-processing/);
        await expect(approveBtn).toContainText('Approving');
        await expect(approveBtn).toBeDisabled();
        await expect(page.locator(`[data-testid="btn-reject-${id}"]`)).toBeDisabled();

        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).not.toBeAttached({ timeout: 10000 });
    });

    test('FINANCE user can access deposits page', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
    });

    test('ADMIN user can access deposits page', async ({ page }) => {
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
    });

    test('OPERATIONS user is blocked from deposits page', async ({ page }) => {
        await loginAs(page, OPERATIONS.mobile, OPERATIONS.password);
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await expect(page.locator('[data-testid="deposits-table"]')).not.toBeVisible();
    });

    test('search form is visible on deposits page', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await expect(page.locator('[data-testid="deposit-search-form"]')).toBeVisible();
        await expect(page.locator('[data-testid="input-deposit-search"]')).toBeVisible();
        await expect(page.locator('[data-testid="btn-deposit-search"]')).toBeVisible();
    });

    test('search input auto-submits via debounce without clicking Search', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await page.waitForLoadState('networkidle');
        // Focus and type to ensure reliable input events
        await page.locator('[data-testid="input-deposit-search"]').focus();
        await page.keyboard.type('DebounceAutoTest', { delay: 50 });
        // Debounce fires at 400ms; wait for navigation/URL change
        await page.waitForURL(/search=DebounceAutoTest/, { timeout: 10000 });
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
    });

    test('sidebar deposit badge element exists in DOM for FINANCE user', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        // Badge span is rendered server-side and managed by wfbo-pending controller
        await expect(page.locator('[data-wfbo-pending-target="depositBadge"]')).toBeAttached();
    });

    test('searching by name filters the deposits table', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await page.fill('[data-testid="input-deposit-search"]', 'Test');
        await page.click('[data-testid="btn-deposit-search"]');
        await page.waitForLoadState('networkidle');

        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
        expect(page.url()).toContain('search=Test');
    });

    test('searching by mobile number filters the deposits table', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        await page.fill('[data-testid="input-deposit-search"]', '+1000');
        await page.click('[data-testid="btn-deposit-search"]');
        await page.waitForLoadState('networkidle');

        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
        expect(page.url()).toContain('search=');
    });

    test('clear button removes search and reloads', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits?search=Test`);
        await expect(page.locator('[data-testid="btn-deposit-search-clear"]')).toBeVisible();
        await page.click('[data-testid="btn-deposit-search-clear"]');
        await page.waitForLoadState('networkidle');

        expect(page.url()).not.toContain('search=');
        await expect(page.locator('[data-testid="deposits-table"]')).toBeVisible();
    });

    test('search preserves status filter', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        await page.fill('[data-testid="input-deposit-search"]', 'User');
        await page.click('[data-testid="btn-deposit-search"]');
        await page.waitForLoadState('networkidle');

        expect(page.url()).toContain('status=pending');
        expect(page.url()).toContain('search=User');
    });

    test('view transaction button opens modal when deposit has an image', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const viewBtn = page.locator('[data-testid^="btn-view-transaction-image-"]').first();
        if (await viewBtn.count() === 0) {
            test.skip();
            return;
        }
        await viewBtn.click();
        await expect(page.locator('#deposit-image-modal')).toBeVisible();
    });

    // ── Overdue pending alert (fixtures seed 4 pending deposits ≥ 5 min old + 1 fresh) ──

    const overdueCountFromAlert = async (text: string): Promise<number> => {
        const m = text.match(/(\d+)\s+deposit request/);
        expect(m, `alert text "${text}" has a count`).not.toBeNull();
        return Number(m![1]);
    };

    test('overdue alert shows the count of pending deposits waiting 5+ minutes', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const alert = page.locator('[data-testid="deposits-overdue-alert"]');
        await expect(alert).toBeVisible();
        await expect(alert).toContainText('pending 5+ minutes');
        await expect(page.locator('[data-testid="btn-view-overdue"]')).toBeVisible();

        const n = await overdueCountFromAlert(await page.locator('[data-testid="overdue-alert-text"]').innerText());
        expect(n).toBeGreaterThanOrEqual(1);
    });

    test('View shows only the overdue pending requests, oldest first', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits`);
        const expected = await overdueCountFromAlert(await page.locator('[data-testid="overdue-alert-text"]').innerText());

        await page.click('[data-testid="btn-view-overdue"]');
        await page.waitForLoadState('networkidle');

        expect(page.url()).toContain('overdue=1');
        expect(page.url()).toContain('status=pending');
        await expect(page.locator('[data-testid="deposits-overdue-alert"]')).toContainText('oldest first');
        await expect(page.locator('[data-testid="btn-clear-overdue"]')).toBeVisible();

        const rows = page.locator('[data-testid^="deposit-row-"]');
        await expect(rows).toHaveCount(Math.min(expected, 15));
        for (const badge of await page.locator('[data-testid^="badge-deposit-"]').all()) {
            await expect(badge).toContainText('Pending');
        }

        // Overdue view is ordered oldest-first: the "Submitted" cell (6th column)
        // of row 1 should be no later than row 2's.
        if (await rows.count() >= 2) {
            const submitted = async (i: number) =>
                Date.parse(await rows.nth(i).locator('td').nth(5).innerText());
            const [a, b] = [await submitted(0), await submitted(1)];
            if (!Number.isNaN(a) && !Number.isNaN(b)) {
                expect(a).toBeLessThanOrEqual(b);
            }
        }
    });

    test('Show all exits overdue mode', async ({ page }) => {
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending&overdue=1`);
        await expect(page.locator('[data-testid="btn-clear-overdue"]')).toBeVisible();
        await page.click('[data-testid="btn-clear-overdue"]');
        await page.waitForLoadState('networkidle');

        expect(page.url()).not.toContain('overdue');
        await expect(page.locator('[data-testid="btn-view-overdue"]')).toBeVisible();
    });

});

test.describe('WFBO Deposit Management — handler restriction', () => {

    test('only the assigned finance handler can approve/reject a deposit for its MOP', async ({ page }) => {
        // The deposit form's dropdown renders "bankName - accountName (accountNumber)",
        // not the PaymentDetail's own `label` field - the bank name itself must be
        // unique to reliably find this option among others.
        const bankName = `Handler Test Bank ${Date.now()}`;
        const ref      = `TXN-HANDLER-${Date.now()}`;

        // Create a payment method assigned to FINANCE.
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/payment-details/new`);
        await page.fill('[data-testid="input-label"]', bankName);
        await page.fill('[data-testid="input-bank-name"]', bankName);
        await page.fill('[data-testid="input-account-name"]', 'Test Account');
        await page.fill('[data-testid="input-account-number"]', `${Date.now()}`.slice(-10));
        await page.selectOption('[data-testid="select-finance-handler"]', { label: 'Finance User (+10000000004)' });
        await page.click('[data-testid="btn-submit"]');
        await expect(page).toHaveURL(`${BASE_URL}/wfbo/payment-details`);

        // Submit a deposit against that bank as a player.
        await loginAs(page, USER.mobile, USER.password);
        await page.goto(`${BASE_URL}/deposit`);
        const bankOption = page.locator('[data-testid="select-payment-detail"] option', { hasText: bankName });
        if (await bankOption.count() === 0) {
            test.skip();
            return;
        }
        const bankValue = await bankOption.getAttribute('value');
        await page.selectOption('[data-testid="select-payment-detail"]', bankValue!);
        await page.fill('[data-testid="input-amount"]', '150.00');
        await page.fill('[data-testid="input-transaction-reference"]', ref);
        await page.click('[data-testid="btn-deposit-submit"]');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        // Not the assigned handler: buttons hidden, "Assigned to" note shown.
        await loginAs(page, FINANCE2.mobile, FINANCE2.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        const row = page.locator('[data-testid^="deposit-row-"]').filter({ hasText: ref });
        await expect(row).toBeVisible();
        const id = (await row.getAttribute('data-testid'))!.replace('deposit-row-', '');
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).not.toBeAttached();
        await expect(page.locator(`[data-testid="btn-reject-${id}"]`)).not.toBeAttached();
        await expect(page.locator(`[data-testid="deposit-review-restricted-${id}"]`)).toContainText('Assigned to Finance User');

        // Not even an admin can bypass the assigned handler.
        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).not.toBeAttached();
        await expect(page.locator(`[data-testid="deposit-review-restricted-${id}"]`)).toBeVisible();

        // The assigned handler can approve it.
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).toBeVisible();
        page.once('dialog', dialog => dialog.accept());
        await page.click(`[data-testid="btn-approve-${id}"]`);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).not.toBeAttached({ timeout: 10000 });
    });

    // Shared setup for the Transfer tests below: two payment methods each
    // assigned to a different finance handler, and a pending deposit against
    // the first one. Returns the pending deposit's row id and both bank
    // names so the caller can drive the Transfer control itself.
    const setUpTransferScenario = async (page: import('@playwright/test').Page) => {
        const bank1Name = `Transfer Test Bank A ${Date.now()}`;
        const bank2Name = `Transfer Test Bank B ${Date.now()}`;
        const ref       = `TXN-TRANSFER-${Date.now()}`;

        const createPaymentDetail = async (bankName: string, handlerLabel: string) => {
            await page.goto(`${BASE_URL}/wfbo/payment-details/new`);
            await page.fill('[data-testid="input-label"]', bankName);
            await page.fill('[data-testid="input-bank-name"]', bankName);
            await page.fill('[data-testid="input-account-name"]', 'Test Account');
            await page.fill('[data-testid="input-account-number"]', `${Date.now()}`.slice(-10));
            await page.selectOption('[data-testid="select-finance-handler"]', { label: handlerLabel });
            await page.click('[data-testid="btn-submit"]');
            await expect(page).toHaveURL(`${BASE_URL}/wfbo/payment-details`);
        };

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await createPaymentDetail(bank1Name, 'Finance User (+10000000004)');
        await createPaymentDetail(bank2Name, 'Finance2 User (+10000000010)');

        await loginAs(page, USER.mobile, USER.password);
        await page.goto(`${BASE_URL}/deposit`);
        const bankOption = page.locator('[data-testid="select-payment-detail"] option', { hasText: bank1Name });
        if (await bankOption.count() === 0) {
            test.skip();
            return null;
        }
        const bankValue = await bankOption.getAttribute('value');
        await page.selectOption('[data-testid="select-payment-detail"]', bankValue!);
        await page.fill('[data-testid="input-amount"]', '150.00');
        await page.fill('[data-testid="input-transaction-reference"]', ref);
        await page.click('[data-testid="btn-deposit-submit"]');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        const row = page.locator('[data-testid^="deposit-row-"]').filter({ hasText: ref });
        await expect(row).toBeVisible();
        const id = (await row.getAttribute('data-testid'))!.replace('deposit-row-', '');

        return { id, bank1Name, bank2Name };
    };

    test('admin can transfer a deposit to another payment method regardless of its assigned handler', async ({ page }) => {
        const scenario = await setUpTransferScenario(page);
        if (scenario === null) return;
        const { id, bank2Name } = scenario;

        // Already logged in as ADMIN from setup - deposit is assigned to
        // FINANCE's payment method, not ADMIN, proving handler assignment
        // no longer gates Transfer.
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        const transferSelect = page.locator(`[data-testid="select-transfer-payment-detail-${id}"]`);
        const transferOption = transferSelect.locator('option', { hasText: bank2Name });
        const transferValue  = await transferOption.getAttribute('value');
        await transferSelect.selectOption(transferValue!);
        page.once('dialog', dialog => dialog.accept());
        await page.click(`[data-testid="btn-transfer-${id}"]`);
        await page.waitForLoadState('networkidle');
        await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();

        // Now assigned to Finance2's payment method - FINANCE2 can approve it.
        await loginAs(page, FINANCE2.mobile, FINANCE2.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).toBeVisible();
        page.once('dialog', dialog => dialog.accept());
        await page.click(`[data-testid="btn-approve-${id}"]`);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).not.toBeAttached({ timeout: 10000 });
    });

    test('a finance handler can no longer transfer a deposit - Transfer is admin-only', async ({ page }) => {
        const scenario = await setUpTransferScenario(page);
        if (scenario === null) return;
        const { id } = scenario;

        // FINANCE is the assigned handler for this deposit's payment method,
        // so they still see Approve/Reject, but the Transfer control itself
        // must be gone.
        await loginAs(page, FINANCE.mobile, FINANCE.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        await expect(page.locator(`[data-testid="btn-approve-${id}"]`)).toBeVisible();
        await expect(page.locator(`[data-testid="btn-transfer-${id}"]`)).not.toBeAttached();
        await expect(page.locator(`[data-testid="select-transfer-payment-detail-${id}"]`)).not.toBeAttached();

        // Server-side enforcement, not just the button being hidden - #[IsGranted]
        // denies before the controller's own CSRF check ever runs, so the
        // token value here doesn't matter for proving this 403.
        const response = await page.request.post(`${BASE_URL}/wfbo/deposits/${id}/transfer`, {
            headers: { 'X-Requested-With': 'XMLHttpRequest' },
            form: { _token: 'irrelevant-blocked-by-role-check-first', payment_detail_id: '1' },
        });
        expect(response.status()).toBe(403);
    });

    test('flags a deposit whose reference is 90%+ similar to an earlier one and lists the match', async ({ page }) => {
        // Two 13-digit refs one middle digit apart (92% similar) - unique per run.
        const ref1 = `${Date.now()}`;
        const mid  = 6;
        const ref2 = ref1.slice(0, mid) + ((Number(ref1[mid]) + 1) % 10) + ref1.slice(mid + 1);

        await loginAs(page, USER.mobile, USER.password);
        for (const ref of [ref1, ref2]) {
            await page.goto(`${BASE_URL}/deposit`);
            const bankOptions = page.locator('[data-testid="select-payment-detail"] option:not([value=""]):not([value="others"])');
            if (await bankOptions.count() === 0) {
                test.skip();
                return;
            }
            await page.selectOption('[data-testid="select-payment-detail"]', { index: 1 });
            await page.fill('[data-testid="input-amount"]', '150.00');
            await page.fill('[data-testid="input-transaction-reference"]', ref);
            await page.click('[data-testid="btn-deposit-submit"]');
            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
        }

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        const row1 = page.locator('[data-testid^="deposit-row-"]').filter({ hasText: ref1 }).first();
        const row2 = page.locator('[data-testid^="deposit-row-"]').filter({ has: page.locator('td', { hasText: new RegExp(`^\\s*${ref2}`) }) });
        await expect(row2).toBeVisible();
        const id1 = (await page.locator(`[data-testid^="deposit-row-"]`).filter({ has: page.locator('td', { hasText: new RegExp(`^\\s*${ref1}`) }) }).getAttribute('data-testid'))!.replace('deposit-row-', '');
        const id2 = (await row2.getAttribute('data-testid'))!.replace('deposit-row-', '');

        // The later request is flagged and lists the earlier one (open on Pending).
        await expect(page.locator(`[data-testid="badge-ref-flagged-${id2}"]`)).toBeVisible();
        const match = page.locator(`[data-testid="similar-ref-${id2}-${id1}"]`);
        await expect(match).toBeVisible();
        await expect(match).toContainText('92%');
        await expect(match).toContainText(`#${id1}`);
        await expect(match).toContainText(ref1);
        await expect(match.locator('.similar-refs__diff')).toHaveText(ref1[mid]);

        // The earlier request was checked before the later existed - not flagged.
        await expect(page.locator(`[data-testid="badge-ref-flagged-${id1}"]`)).toHaveCount(0);
        await expect(row1).toBeVisible();
        await row2.scrollIntoViewIfNeeded();
    });

    test('flags a deposit whose reference has two digits swapped', async ({ page }) => {
        // Swap the 5th and 9th digits of a unique 13-digit ref (forced to differ).
        const base = `${Date.now()}`;
        const ref1 = base.slice(0, 4) + '3' + base.slice(5, 8) + '7' + base.slice(9);
        const ref2 = ref1.slice(0, 4) + '7' + ref1.slice(5, 8) + '3' + ref1.slice(9);

        await loginAs(page, USER.mobile, USER.password);
        for (const ref of [ref1, ref2]) {
            await page.goto(`${BASE_URL}/deposit`);
            const bankOptions = page.locator('[data-testid="select-payment-detail"] option:not([value=""]):not([value="others"])');
            if (await bankOptions.count() === 0) {
                test.skip();
                return;
            }
            await page.selectOption('[data-testid="select-payment-detail"]', { index: 1 });
            await page.fill('[data-testid="input-amount"]', '150.00');
            await page.fill('[data-testid="input-transaction-reference"]', ref);
            await page.click('[data-testid="btn-deposit-submit"]');
            await expect(page.locator('[data-testid="flash-success"]')).toBeVisible();
        }

        await loginAs(page, ADMIN.mobile, ADMIN.password);
        await page.goto(`${BASE_URL}/wfbo/deposits?status=pending`);
        const rowOf = (ref: string) => page.locator('[data-testid^="deposit-row-"]')
            .filter({ has: page.locator('td', { hasText: new RegExp(`^\\s*${ref}`) }) });
        const id1 = (await rowOf(ref1).getAttribute('data-testid'))!.replace('deposit-row-', '');
        const id2 = (await rowOf(ref2).getAttribute('data-testid'))!.replace('deposit-row-', '');

        await expect(page.locator(`[data-testid="badge-ref-flagged-${id2}"]`)).toBeVisible();
        const match = page.locator(`[data-testid="similar-ref-${id2}-${id1}"]`);
        await expect(match).toContainText('92%');
        await expect(match.locator('.similar-refs__diff')).toHaveText(['3', '7']);
        await rowOf(ref2).scrollIntoViewIfNeeded();
    });

});
