/**
 * P2-10 — /api/health/deep aggregates DB, worker, outbox, SMTP checks.
 * PR-B.4 — now requires Authorization: Bearer CRON_SECRET.
 */

jest.mock('@/lib/prisma', () => ({
    prisma: {
        $queryRaw: jest.fn(),
        monitor: { findFirst: jest.fn() },
        notificationOutbox: { count: jest.fn() },
    },
}));

import { prisma } from '@/lib/prisma';
import { GET } from '../route';

const $queryRaw = prisma.$queryRaw as jest.Mock;
const monitorFindFirst = prisma.monitor.findFirst as jest.Mock;
const outboxCount = prisma.notificationOutbox.count as jest.Mock;

const CRON_SECRET = 'this-is-a-long-enough-secret-1';

// PR-B.4: build a NextRequest-shaped object carrying the Authorization
// header that requireCronSecret expects.
function makeReq(auth: string = `Bearer ${CRON_SECRET}`) {
    return {
        url: 'http://localhost:3000/api/health/deep',
        headers: {
            get: (k: string) => (k.toLowerCase() === 'authorization' ? auth : null),
        },
    } as unknown as Parameters<typeof GET>[0];
}

beforeEach(() => {
    jest.clearAllMocks();
    delete process.env.SMTP_HOST;
    delete process.env.SMTP_PORT;
    process.env.CRON_SECRET = CRON_SECRET;
});

describe('GET /api/health/deep', () => {
    it('200 when all checks pass and SMTP is not configured', async () => {
        $queryRaw.mockResolvedValueOnce([{ 1: 1 }]);
        monitorFindFirst.mockResolvedValueOnce({ lastCheckAt: new Date(Date.now() - 30_000) });
        outboxCount.mockResolvedValueOnce(0);
        const res = await GET(makeReq());
        expect(res.status).toBe(200);
        const body = await res.json();
        expect(body.status).toBe('healthy');
        const smtp = body.checks.find((c: { name: string }) => c.name === 'smtp');
        expect(smtp).toMatchObject({ ok: true, detail: 'not configured' });
    });

    it('503 when DB query throws', async () => {
        $queryRaw.mockRejectedValueOnce(new Error('connection refused'));
        monitorFindFirst.mockResolvedValueOnce({ lastCheckAt: new Date() });
        outboxCount.mockResolvedValueOnce(0);
        const res = await GET(makeReq());
        expect(res.status).toBe(503);
        const body = await res.json();
        expect(body.status).toBe('degraded');
        expect(body.checks.find((c: { name: string }) => c.name === 'db').ok).toBe(false);
    });

    it('503 when last monitor check is older than 5 min', async () => {
        $queryRaw.mockResolvedValueOnce([{ 1: 1 }]);
        monitorFindFirst.mockResolvedValueOnce({ lastCheckAt: new Date(Date.now() - 10 * 60 * 1000) });
        outboxCount.mockResolvedValueOnce(0);
        const res = await GET(makeReq());
        expect(res.status).toBe(503);
        const body = await res.json();
        const worker = body.checks.find((c: { name: string }) => c.name === 'worker');
        expect(worker.ok).toBe(false);
        expect(worker.detail).toMatch(/STALE/);
    });

    it('503 when outbox backlog exceeds budget', async () => {
        $queryRaw.mockResolvedValueOnce([{ 1: 1 }]);
        monitorFindFirst.mockResolvedValueOnce({ lastCheckAt: new Date() });
        outboxCount.mockResolvedValueOnce(10_000);
        const res = await GET(makeReq());
        expect(res.status).toBe(503);
    });

    it('treats "no active monitors yet" as healthy', async () => {
        $queryRaw.mockResolvedValueOnce([{ 1: 1 }]);
        monitorFindFirst.mockResolvedValueOnce(null);
        outboxCount.mockResolvedValueOnce(0);
        const res = await GET(makeReq());
        expect(res.status).toBe(200);
    });

    // PR-B.4: auth tests
    it('401 when Authorization header missing', async () => {
        const res = await GET(makeReq('') as never);
        expect(res.status).toBe(401);
    });

    it('401 when Bearer secret is wrong', async () => {
        const res = await GET(makeReq('Bearer wrong-secret-not-the-real-one'));
        expect(res.status).toBe(401);
    });
});
