/**
 * SSL Certificate-Expiry Monitor Strategy (new 2026-05-30).
 *
 * A dedicated cert-watch monitor: open a raw TLS connection using Node's
 * built-in tls module (no new dependency), read the peer certificate, and
 * decide UP/DOWN purely on certificate health, independent of any HTTP
 * status. Complements the HTTP strategy's opportunistic cert capture
 * (Deliverable 1) for endpoints where you care about the cert itself.
 *
 * Config mapping (reuses existing Monitor columns, no migration):
 *   - url / hostname: the TLS host (protocol prefix + path stripped).
 *   - port: TLS port (defaults to 443).
 *   - tlsExpiryWarningDays: go DOWN when the cert expires within this many
 *     days (sourced from Monitor.tlsExpiryWarningDays, default 14).
 *   - ignoreTlsErrors: when true, an untrusted chain still reports UP as
 *     long as the cert is within its validity window.
 *
 * DOWN cases: handshake error, cert expired / not-yet-valid, or cert expires
 * within the warning window. The peer cert expiry + issuer are surfaced when
 * available so the existing dashboard card + detail page render them.
 */
import type { MonitorForCheck } from '@/types';
import type { MonitorStrategy, StrategyCheckResult, ErrorClass } from './index';
import { resolveAndValidate } from '@/lib/network-security/ssrf-guard';
import { env } from '@/lib/env';

function stripHost(raw: string): string {
    let host = raw.trim().replace(/^[a-z]+:\/\//i, '');
    host = host.split('/')[0];
    if (host.includes(':')) host = host.split(':')[0];
    return host;
}

export class SslMonitorStrategy implements MonitorStrategy {
    async check(monitor: MonitorForCheck): Promise<StrategyCheckResult> {
        const startTime = Date.now();
        const tls = await import('tls');

        const host = stripHost(monitor.url || monitor.hostname || '');
        const port = monitor.port || 443;
        const warningDays = monitor.tlsExpiryWarningDays ?? 14;
        const timeoutMs = (monitor.timeoutSeconds || 10) * 1000;

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

        let resolvedIp: string;
        try {
            const target = await resolveAndValidate(host, {
                allowPrivate: env.ALLOW_PRIVATE_NETWORK,
                allowLoopback: env.ALLOW_LOOPBACK_PROBES,
            });
            resolvedIp = target.ip;
        } catch (err: unknown) {
            return {
                status: 'down',
                duration: Date.now() - startTime,
                statusCode: 0,
                errorMessage: err instanceof Error ? err.message : 'Blocked or unresolvable host',
                errorClass: 'DNS_FAIL',
            };
        }

        let status: 'up' | 'down' = 'down';
        let errorMessage = '';
        let errorClass: ErrorClass | null = null;
        let tlsValid = false;
        let tlsExpiresAt: Date | null = null;
        let tlsIssuer: string | null = null;

        try {
            await new Promise<void>((resolve, reject) => {
                const socket = tls.connect({
                    host: resolvedIp,
                    port,
                    servername: host,
                    rejectUnauthorized: !monitor.ignoreTlsErrors,
                    timeout: timeoutMs,
                });

                const fail = (err: Error) => {
                    socket.destroy();
                    reject(err);
                };

                socket.on('error', (err: Error) => fail(err));
                socket.on('timeout', () => fail(Object.assign(new Error('TLS handshake timed out'), { code: 'ABORT_ERR' })));

                socket.on('secureConnect', () => {
                    try {
                        const cert = socket.getPeerCertificate();
                        tlsValid = socket.authorized || monitor.ignoreTlsErrors;

                        if (!cert || Object.keys(cert).length === 0) {
                            errorClass = 'TLS_INVALID';
                            errorMessage = 'No peer certificate presented';
                            socket.end();
                            return resolve();
                        }

                        const issuer = cert.issuer as unknown as Record<string, string> | undefined;
                        tlsIssuer = issuer?.O || issuer?.CN || 'Unknown';
                        if (cert.valid_to) tlsExpiresAt = new Date(cert.valid_to);
                        const notBefore = cert.valid_from ? new Date(cert.valid_from) : null;

                        const now = Date.now();
                        if (notBefore && notBefore.getTime() > now) {
                            errorClass = 'TLS_INVALID';
                            errorMessage = 'Certificate not valid until ' + notBefore.toUTCString();
                        } else if (tlsExpiresAt && tlsExpiresAt.getTime() <= now) {
                            errorClass = 'TLS_INVALID';
                            errorMessage = 'Certificate expired on ' + tlsExpiresAt.toUTCString();
                        } else if (tlsExpiresAt) {
                            const daysLeft = Math.floor((tlsExpiresAt.getTime() - now) / 86400000);
                            if (daysLeft <= warningDays) {
                                errorClass = 'TLS_INVALID';
                                errorMessage = 'Certificate expires in ' + daysLeft + ' day(s) (warning threshold ' + warningDays + ')';
                            } else {
                                status = 'up';
                            }
                        } else {
                            errorClass = 'TLS_INVALID';
                            errorMessage = 'Certificate has no parseable expiry date';
                        }

                        socket.end();
                        resolve();
                    } catch (e) {
                        fail(e instanceof Error ? e : new Error(String(e)));
                    }
                });
            });
        } catch (err: unknown) {
            status = 'down';
            errorMessage = err instanceof Error ? err.message : 'TLS connection failed';
            errorClass = 'TLS_INVALID';
        }

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