import { beforeEach, describe, expect, it, vi } from 'vitest';
import { BROWSER_EXECUTION_CAPABILITY } from '@/lib/runners/constants';
import { RUN_IN_PROGRESS_STATUSES, RUN_TERMINAL_STATUSES, TEST_STATUS } from '@/types';

const { queryRaw, startLocalBrowserRun, hasLocalBrowserRunCapacity, getActiveLocalBrowserRunCount, getMaxLocalBrowserRunCount } = vi.hoisted(() => ({
    queryRaw: vi.fn(),
    startLocalBrowserRun: vi.fn(),
    hasLocalBrowserRunCapacity: vi.fn(),
    getActiveLocalBrowserRunCount: vi.fn(),
    getMaxLocalBrowserRunCount: vi.fn(),
}));

vi.mock('@/lib/core/prisma', () => ({
    prisma: {
        $queryRaw: queryRaw,
    },
}));

vi.mock('@/lib/runtime/local-browser-runner', () => ({
    startLocalBrowserRun,
    hasLocalBrowserRunCapacity,
    getActiveLocalBrowserRunCount,
    getMaxLocalBrowserRunCount,
}));

process.env.SKYTEST_BROWSER_WORKER = 'true';

const {
    dispatchBrowserRun,
    dispatchNextQueuedBrowserRun,
} = await import('@/lib/runtime/browser-run-dispatcher');

describe('browser-run-dispatcher', () => {
    beforeEach(() => {
        queryRaw.mockReset();
        startLocalBrowserRun.mockReset();
        startLocalBrowserRun.mockResolvedValue(undefined);
        hasLocalBrowserRunCapacity.mockReset();
        hasLocalBrowserRunCapacity.mockReturnValue(true);
        getActiveLocalBrowserRunCount.mockReset();
        getActiveLocalBrowserRunCount.mockReturnValue(0);
        getMaxLocalBrowserRunCount.mockReset();
        getMaxLocalBrowserRunCount.mockReturnValue(1);
    });

    it('dispatches next queued browser run when a candidate is claimed', async () => {
        queryRaw.mockResolvedValueOnce([{ id: 'run-browser-1' }]);

        const dispatched = await dispatchNextQueuedBrowserRun();

        expect(dispatched).toBe(true);
        expect(startLocalBrowserRun).toHaveBeenCalledWith('run-browser-1');
        const [query] = queryRaw.mock.calls[0];
        const sql = query.strings.join('');
        expect(sql).toContain('tr."requiredCapability" =');
        expect(sql).toContain('tr.status =');
        expect(query.values).toContain(TEST_STATUS.QUEUED);
        expect(query.values).toContain(BROWSER_EXECUTION_CAPABILITY);
    });

    it('does not dispatch when no queued browser run is claimable', async () => {
        queryRaw.mockResolvedValueOnce([]);

        const dispatched = await dispatchNextQueuedBrowserRun();

        expect(dispatched).toBe(false);
        expect(startLocalBrowserRun).not.toHaveBeenCalled();
    });

    it('filters by run id for targeted browser dispatch', async () => {
        queryRaw.mockResolvedValueOnce([{ id: 'run-browser-2' }]);

        const dispatched = await dispatchBrowserRun('run-browser-2');

        expect(dispatched).toBe(true);
        expect(startLocalBrowserRun).toHaveBeenCalledWith('run-browser-2');
        const [query] = queryRaw.mock.calls[0];
        expect(query.values).toContain('run-browser-2');
    });

    it('uses active-run states for concurrency gating so queued Android runs do not block browser dispatch', async () => {
        queryRaw.mockResolvedValueOnce([{ id: 'run-browser-3' }]);

        const dispatched = await dispatchNextQueuedBrowserRun();

        expect(dispatched).toBe(true);
        const [query] = queryRaw.mock.calls[0];
        const sql = query.strings.join('');
        expect(sql).toContain('activeTr.status IN');
        expect(sql).toContain('LEAST(p."maxConcurrentRuns"');
        expect(query.values).toEqual(expect.arrayContaining([...RUN_IN_PROGRESS_STATUSES]));
        const queuedValueCount = query.values.filter((value: unknown) => value === TEST_STATUS.QUEUED).length;
        expect(queuedValueCount).toBe(1);
    });

    it('excludes session members whose session already has a terminal member so a partial session cannot restart', async () => {
        queryRaw.mockResolvedValueOnce([{ id: 'run-browser-4' }]);

        const dispatched = await dispatchNextQueuedBrowserRun();

        expect(dispatched).toBe(true);
        const [query] = queryRaw.mock.calls[0];
        const sql = query.strings.join('');
        expect(sql).toContain('NOT EXISTS');
        expect(sql).toContain('settledMember."runSessionId" = tr."runSessionId"');
        expect(query.values).toEqual(expect.arrayContaining([...RUN_TERMINAL_STATUSES]));
    });

    it('skips dispatch when local pod capacity is full', async () => {
        hasLocalBrowserRunCapacity.mockReturnValue(false);

        const dispatched = await dispatchNextQueuedBrowserRun();

        expect(dispatched).toBe(false);
        expect(queryRaw).not.toHaveBeenCalled();
        expect(startLocalBrowserRun).not.toHaveBeenCalled();
    });
});
