/**
 * DNS Monitor Strategy (re-added 2026-05-30).
 *
 * The 'dns' type was removed in audit2-17f because it had NO strategy —
 * the engine routed it through the unsupported-type branch and wrote a
 * synthetic 'down' every tick. This implementation resolves a DNS record
 * set using Node's built-in `dns/promises` resolver (no new dependency).
 *
 * Config mapping (reuses existing Monitor columns — no migration):
 *   - `url` / `hostname`: the domain to resolve (protocol prefix stripped).
 *   - `method`: the record type to query — A | AAAA | CNAME | MX | TXT.
 *     Reuses the existing `method` column (defaults to 'GET', treated as A).
 *   - `keyword` (+ `keywordShouldContain`): optional assertion that a
 *     substring is present (or absent) in the resolved values.
 *
 * UP   = the record set is non-empty AND the optional keyword assertion holds.
 * DOWN = resolver error (NXDOMAIN / SERVFAIL / timeout), empty record set,
 *        or a failed keyword assertion.
 */
import dns from 'dns/promises';
import type { MonitorForCheck } from '@/types';
import type { MonitorStrategy, StrategyCheckResult, ErrorClass } from './index';

type DnsRecordType = 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT';

const SUPPORTED: DnsRecordType[] = ['A', 'AAAA', 'CNAME', 'MX', 'TXT'];

function normalizeRecordType(method: string | null | undefined): DnsRecordType {
    const m = (method || '').trim().toUpperCase();
    // 'GET' is the column default for non-HTTP monitors; treat it as A.
    if (m === 'GET' || m === '') return 'A';
    return (SUPPORTED.includes(m as DnsRecordType) ? m : 'A') as DnsRecordType;
}

function stripHost(raw: string): string {
    let host = raw.trim().replace(/^[a-z]+:\/\//i, '');
    // drop any path / port / query suffix
    host = host.split('/')[0].split(':')[0];
    return host;
}

export class DnsMonitorStrategy implements MonitorStrategy {
    async check(monitor: MonitorForCheck): Promise<StrategyCheckResult> {
        const startTime = Date.now();
        const recordType = normalizeRecordType(monitor.method);
        const host = stripHost(monitor.url || monitor.hostname || '');

        let status: 'up' | 'down' = 'down';
        let errorMessage = '';
        let errorClass: ErrorClass | null = null;

        if (!host) {
            return {
                status: 'down',
                duration: Date.now() - startTime,
                statusCode: 0,
                errorMessage: 'No domain configured for DNS monitor',
                errorClass: 'DNS_FAIL',
            };
        }

        try {
            const values = await this.resolve(recordType, host);

            if (values.length === 0) {
                return {
                    status: 'down',
                    duration: Date.now() - startTime,
                    statusCode: 0,
                    errorMessage: `No ${recordType} record found for ${host}`,
                    errorClass: 'DNS_FAIL',
                };
            }

            status = 'up';

            // Optional keyword assertion against the resolved values.
            if (monitor.keyword) {
                const haystack = values.join(' ');
                const found = haystack.includes(monitor.keyword);
                if (monitor.keywordShouldContain && !found) {
                    status = 'down';
                    errorClass = 'KEYWORD_MISS';
                    errorMessage = `Expected "${monitor.keyword}" in ${recordType} records but it was absent`;
                } else if (!monitor.keywordShouldContain && found) {
                    status = 'down';
                    errorClass = 'KEYWORD_MISS';
                    errorMessage = `"${monitor.keyword}" present in ${recordType} records (should be absent)`;
                }
            }
        } catch (err: unknown) {
            status = 'down';
            errorMessage = err instanceof Error ? err.message : 'DNS resolution failed';
            errorClass = 'DNS_FAIL';
        }

        return {
            status,
            duration: Date.now() - startTime,
            statusCode: 0,
            errorMessage,
            errorClass: status === 'up' ? null : errorClass,
        };
    }

    private async resolve(recordType: DnsRecordType, host: string): Promise<string[]> {
        switch (recordType) {
            case 'A':
                return dns.resolve4(host);
            case 'AAAA':
                return dns.resolve6(host);
            case 'CNAME':
                return dns.resolveCname(host);
            case 'MX':
                return (await dns.resolveMx(host)).map((m) => `${m.priority} ${m.exchange}`);
            case 'TXT':
                return (await dns.resolveTxt(host)).map((chunks) => chunks.join(''));
            default:
                return dns.resolve4(host);
        }
    }
}
