import { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync } from 'fs';
import { join } from 'path';
import { hostname as osHostname } from 'os';
import { isIP } from 'net';
import { X509Certificate, createPrivateKey } from 'crypto';
import { logger } from '../logger.js';
import selfsigned from 'selfsigned';

export interface EnsureCertOpts {
  /** Override host detection (tests). */
  hostname?: string;
}

const CERT_FILE = 'cert.pem';
const KEY_FILE = 'key.pem';

function sanHosts(extra: string[], hostname: string): string[] {
  const base = ['localhost', '127.0.0.1', '::1'];
  const host = hostname.trim();
  if (host && host !== 'localhost') base.push(host);
  return [...new Set([...base, ...extra.filter((h) => h.trim().length > 0)])];
}

function isIp(value: string): boolean {
  return isIP(value) !== 0;
}

// Validates that the cert and key match each other; does NOT check expiry by design
// (the caller regenerates on restart; the Settings UI surfaces expiry).
function readValidPair(dir: string): { cert: string; key: string } | null {
  const certPath = join(dir, CERT_FILE);
  const keyPath = join(dir, KEY_FILE);
  if (!existsSync(certPath) || !existsSync(keyPath)) return null;
  try {
    const cert = readFileSync(certPath, 'utf-8');
    const key = readFileSync(keyPath, 'utf-8');
    const x509 = new X509Certificate(cert);
    const priv = createPrivateKey(key);
    if (!x509.checkPrivateKey(priv)) return null;
    return { cert, key };
  } catch (err) {
    logger.warn(`[self-signed] existing cert/key unreadable, will regenerate: ${(err as Error).message}`);
    return null;
  }
}

export function ensureSelfSignedCert(
  dir: string,
  extraHosts: string[],
  opts: EnsureCertOpts = {},
): { cert: string; key: string } {
  const existing = readValidPair(dir);
  if (existing) return existing;

  mkdirSync(dir, { recursive: true });
  const hosts = sanHosts(extraHosts, opts.hostname ?? osHostname());
  const altNames = hosts.map((h) => (isIp(h) ? { type: 7, ip: h } : { type: 2, value: h }));
  const pems = selfsigned.generate([{ name: 'commonName', value: hosts[0] }], {
    keySize: 2048,
    days: 825,
    algorithm: 'sha256',
    extensions: [{ name: 'subjectAltName', altNames }],
  });

  const certPath = join(dir, CERT_FILE);
  const keyPath = join(dir, KEY_FILE);
  writeFileSync(`${certPath}.tmp`, pems.cert, { mode: 0o600 });
  writeFileSync(`${keyPath}.tmp`, pems.private, { mode: 0o600 });
  renameSync(`${keyPath}.tmp`, keyPath);
  renameSync(`${certPath}.tmp`, certPath);
  return { cert: pems.cert, key: pems.private };
}
