/**
 * Unit tests for DnsMonitorStrategy (re-added 2026-05-30).
 *
 * The 'dns' type was removed in audit2-17f because it had NO strategy
 * implementation — the engine wrote a synthetic 'down' every tick. This
 * re-adds it properly using Node's dns/promises resolver. The monitor
 * resolves a record set (A / AAAA / CNAME / MX / TXT, selected via the
 * reused `method` column) and is UP when the record set is non-empty.
 * An optional `keyword` asserts a substring is present in the resolved
 * values.
 */
import { DnsMonitorStrategy } from '../dns.strategy';
import type { MonitorForCheck } from '@/types';
import dns from 'dns/promises';

jest.mock('dns/promises', () => ({
    resolve4: jest.fn(),
    resolve6: jest.fn(),
    resolveCname: jest.fn(),
    resolveMx: jest.fn(),
    resolveTxt: jest.fn(),
}));

const r4 = dns.resolve4 as jest.MockedFunction<typeof dns.resolve4>;
const r6 = dns.resolve6 as jest.MockedFunction<typeof dns.resolve6>;
const rc = dns.resolveCname as jest.MockedFunction<typeof dns.resolveCname>;
const rmx = dns.resolveMx as jest.MockedFunction<typeof dns.resolveMx>;
const rtxt = dns.resolveTxt as jest.MockedFunction<typeof dns.resolveTxt>;

const base: MonitorForCheck = {
    id: 1,
    name: 'dns-test',
    type: 'dns',
    url: 'example.com',
    hostname: null,
    port: null,
    method: 'A',
    headers: null,
    body: null,
    timeoutSeconds: 5,
    acceptedStatusCodes: '["200-299"]',
    keyword: null,
    keywordShouldContain: true,
    ignoreTlsErrors: false,
    intervalSeconds: 60,
    retries: 2,
    region: 'Global',
};

beforeEach(() => {
    r4.mockReset(); r6.mockReset(); rc.mockReset(); rmx.mockReset(); rtxt.mockReset();
});

describe('DnsMonitorStrategy', () => {
    it('returns UP when an A record resolves', async () => {
        r4.mockResolvedValue(['93.184.216.34']);
        const result = await new DnsMonitorStrategy().check({ ...base, method: 'A' });
        expect(result.status).toBe('up');
        expect(r4).toHaveBeenCalledWith('example.com');
    });

    it('returns DOWN with DNS_FAIL when the record set is empty', async () => {
        r4.mockResolvedValue([]);
        const result = await new DnsMonitorStrategy().check({ ...base, method: 'A' });
        expect(result.status).toBe('down');
        expect(result.errorClass).toBe('DNS_FAIL');
        expect(result.errorMessage).toMatch(/no .* record/i);
    });

    it('returns DOWN with DNS_FAIL when the resolver throws NXDOMAIN', async () => {
        r4.mockRejectedValue(Object.assign(new Error('queryA ENOTFOUND example.com'), { code: 'ENOTFOUND' }));
        const result = await new DnsMonitorStrategy().check({ ...base, method: 'A' });
        expect(result.status).toBe('down');
        expect(result.errorClass).toBe('DNS_FAIL');
    });

    it('resolves AAAA, CNAME, MX and TXT records based on the method field', async () => {
        r6.mockResolvedValue(['2606:2800:220:1:248:1893:25c8:1946']);
        rc.mockResolvedValue(['target.example.com']);
        rmx.mockResolvedValue([{ exchange: 'mail.example.com', priority: 10 }]);
        rtxt.mockResolvedValue([['v=spf1 include:_spf.example.com ~all']]);

        expect((await new DnsMonitorStrategy().check({ ...base, method: 'AAAA' })).status).toBe('up');
        expect((await new DnsMonitorStrategy().check({ ...base, method: 'CNAME' })).status).toBe('up');
        expect((await new DnsMonitorStrategy().check({ ...base, method: 'MX' })).status).toBe('up');
        expect((await new DnsMonitorStrategy().check({ ...base, method: 'TXT' })).status).toBe('up');
        expect(r6).toHaveBeenCalled();
        expect(rc).toHaveBeenCalled();
        expect(rmx).toHaveBeenCalled();
        expect(rtxt).toHaveBeenCalled();
    });

    it('asserts the keyword is present in resolved values (UP when found)', async () => {
        rtxt.mockResolvedValue([['v=spf1 include:_spf.google.com ~all']]);
        const result = await new DnsMonitorStrategy().check({
            ...base, method: 'TXT', keyword: '_spf.google.com', keywordShouldContain: true,
        });
        expect(result.status).toBe('up');
    });

    it('returns DOWN with KEYWORD_MISS when keyword is required but absent', async () => {
        r4.mockResolvedValue(['1.2.3.4']);
        const result = await new DnsMonitorStrategy().check({
            ...base, method: 'A', keyword: '9.9.9.9', keywordShouldContain: true,
        });
        expect(result.status).toBe('down');
        expect(result.errorClass).toBe('KEYWORD_MISS');
    });

    it('strips a protocol prefix from the url before resolving', async () => {
        r4.mockResolvedValue(['1.2.3.4']);
        await new DnsMonitorStrategy().check({ ...base, url: 'https://example.com/path', method: 'A' });
        expect(r4).toHaveBeenCalledWith('example.com');
    });

    it('defaults to an A lookup when method is unset/GET (legacy default)', async () => {
        r4.mockResolvedValue(['1.2.3.4']);
        const result = await new DnsMonitorStrategy().check({ ...base, method: 'GET' });
        expect(result.status).toBe('up');
        expect(r4).toHaveBeenCalledWith('example.com');
    });
});
