/**
 * /api/notifications/test SSRF defense regression test.
 *
 * Auditor finding (2026-06-01, MEDIUM): user-supplied `smtpHost` was
 * passed to nodemailer with NO SSRF validation. The pre-existing
 * `resolveHostForIpv4` is just a DNS-forcer (no private-range check)
 * AND was only invoked when `smtpIgnoreTls === true`. An EDITOR could
 * point smtpHost at:
 *   - 169.254.169.254 (cloud metadata exfiltration)
 *   - 127.0.0.1 / RFC1918 ranges (internal port-scanning)
 *   - link-local addresses
 *
 * Fix: route the user-supplied smtpHost through `resolveAndValidate`
 * from network-security/ssrf-guard, unconditionally. Same guard the
 * HTTP probe uses. Pass the resolved IP to nodemailer (defeats DNS
 * rebinding) while keeping the original hostname for SNI.
 */
process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET || 'this-is-a-long-enough-secret-1';

jest.mock('next-auth/next', () => ({ getServerSession: jest.fn() }));
jest.mock('@/lib/network-security/ssrf-guard', () => {
    class BlockedHostError extends Error {
        constructor(hostname: string, ip?: string) {
            super(`Blocked host: ${hostname}${ip ? ` (${ip})` : ''}`);
        }
    }
    return {
        resolveAndValidate: jest.fn(),
        BlockedHostError,
    };
});
jest.mock('@/lib/email/transport', () => ({
    createTransport: jest.fn(),
    resolveHostForIpv4: jest.fn(),
}));
jest.mock('@/lib/email/logger', () => ({ logSmtp: jest.fn() }));

import { POST } from '../route';
import { getServerSession } from 'next-auth/next';
import { resolveAndValidate, BlockedHostError } from '@/lib/network-security/ssrf-guard';
import { createTransport } from '@/lib/email/transport';

const mockSession = getServerSession as jest.Mock;
const mockResolveAndValidate = resolveAndValidate as jest.Mock;
const mockCreateTransport = createTransport as jest.Mock;

function makePostRequest(body: unknown): Request {
    return new Request('http://localhost/api/notifications/test', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(body),
    });
}

const baseSmtpConfig = {
    smtpHost: 'smtp.example.com',
    smtpPort: 587,
    smtpUser: 'user@example.com',
    smtpPass: 'secret',
    fromEmail: 'user@example.com',
};

describe('POST /api/notifications/test — SMTP SSRF defense (auditor T1B)', () => {
    beforeEach(() => {
        jest.clearAllMocks();
        mockSession.mockResolvedValue({ user: { id: '1', role: 'ADMIN' } });
        mockCreateTransport.mockReturnValue({
            verify: jest.fn().mockResolvedValue(undefined),
            sendMail: jest.fn().mockResolvedValue({ messageId: 'abc-123' }),
        });
    });

    it('rejects smtpHost that resolves to a blocked range with 400', async () => {
        mockResolveAndValidate.mockRejectedValue(new BlockedHostError('169.254.169.254'));

        const res = await POST(makePostRequest({
            type: 'smtp',
            config: { ...baseSmtpConfig, smtpHost: '169.254.169.254' },
        }));

        expect(res.status).toBe(400);
        const body = await res.json();
        expect(body.error).toMatch(/(blocked|forbidden|not allowed|rejected)/i);
        expect(mockCreateTransport).not.toHaveBeenCalled();
    });

    it('rejects loopback smtpHost (127.0.0.1)', async () => {
        mockResolveAndValidate.mockRejectedValue(new BlockedHostError('127.0.0.1'));

        const res = await POST(makePostRequest({
            type: 'smtp',
            config: { ...baseSmtpConfig, smtpHost: '127.0.0.1' },
        }));

        expect(res.status).toBe(400);
        expect(mockCreateTransport).not.toHaveBeenCalled();
    });

    it('passes the resolved public IP to nodemailer (not the original hostname)', async () => {
        mockResolveAndValidate.mockResolvedValue({
            ip: '203.0.113.10',
            family: 4,
            hostname: 'smtp.example.com',
        });

        await POST(makePostRequest({ type: 'smtp', config: baseSmtpConfig }));

        expect(mockCreateTransport).toHaveBeenCalledTimes(1);
        const transportConfig = mockCreateTransport.mock.calls[0][0];
        expect(transportConfig.host).toBe('203.0.113.10');
        expect(transportConfig.servername).toBe('smtp.example.com');
    });

    it('validates BEFORE creating the transport, regardless of smtpIgnoreTls', async () => {
        mockResolveAndValidate.mockRejectedValue(new BlockedHostError('10.0.0.5'));

        // smtpIgnoreTls=false used to skip the (broken) resolveHostForIpv4 path
        // entirely. The new guard is unconditional.
        const res = await POST(makePostRequest({
            type: 'smtp',
            config: { ...baseSmtpConfig, smtpHost: '10.0.0.5', smtpIgnoreTls: false },
        }));

        expect(res.status).toBe(400);
        expect(mockResolveAndValidate).toHaveBeenCalledWith('10.0.0.5');
        expect(mockCreateTransport).not.toHaveBeenCalled();
    });

    it('VIEWER role is forbidden even before reaching the SSRF guard', async () => {
        mockSession.mockResolvedValue({ user: { id: '2', role: 'VIEWER' } });

        const res = await POST(makePostRequest({ type: 'smtp', config: baseSmtpConfig }));

        expect(res.status).toBe(403);
        expect(mockResolveAndValidate).not.toHaveBeenCalled();
        expect(mockCreateTransport).not.toHaveBeenCalled();
    });
});
