import { spawn } from 'node:child_process';

import type { DnsProvider, DnsRecordSpec } from './types';

function getRfc2136Config() {
  return {
    server: process.env['RFC2136_SERVER'] ?? '',
    keyName: process.env['RFC2136_KEY_NAME'] ?? '',
    keySecret: process.env['RFC2136_KEY_SECRET'] ?? '',
    ttl: Number.parseInt(process.env['RFC2136_TTL'] ?? '300', 10),
  };
}

function buildNsupdateScript(record: DnsRecordSpec, mode: 'add' | 'delete'): string {
  const config = getRfc2136Config();
  const ttl = record.ttl ?? config.ttl;
  const lines = [`server ${config.server}`, `key hmac-md5:${config.keyName} ${config.keySecret}`];

  if (mode === 'delete') {
    if (record.recordType === 'SRV') {
      lines.push(`update delete ${record.fqdn} SRV`);
    } else {
      lines.push(`update delete ${record.fqdn} A`);
    }
  } else if (record.recordType === 'SRV') {
    const priority = record.srvPriority ?? 0;
    const weight = record.srvWeight ?? 5;
    const port = record.port ?? 25565;
    const target = record.target.endsWith('.') ? record.target : `${record.target}.`;
    lines.push(
      `update add ${record.fqdn} ${ttl} SRV ${priority} ${weight} ${port} ${target}`,
    );
  } else {
    lines.push(`update add ${record.fqdn} ${ttl} A ${record.target}`);
  }

  lines.push('send');
  return lines.join('\n');
}

async function runNsupdate(script: string): Promise<void> {
  await new Promise<void>((resolve, reject) => {
    const child = spawn('nsupdate', ['-v'], {
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    let stderr = '';
    child.stderr.on('data', (chunk: Buffer) => {
      stderr += chunk.toString('utf8');
    });
    child.on('error', reject);
    child.on('close', (code) => {
      if (code === 0) {
        resolve();
        return;
      }
      reject(new Error(stderr.trim() || `nsupdate exited with code ${code}`));
    });
    child.stdin.write(script);
    child.stdin.end();
  });
}

export class Rfc2136DnsProvider implements DnsProvider {
  readonly name = 'rfc2136';

  private assertConfigured(): void {
    const config = getRfc2136Config();
    if (!config.server || !config.keyName || !config.keySecret) {
      throw new Error(
        'RFC2136 DNS provider requires RFC2136_SERVER, RFC2136_KEY_NAME and RFC2136_KEY_SECRET',
      );
    }
  }

  async syncRecord(record: DnsRecordSpec): Promise<void> {
    this.assertConfigured();
    const script = buildNsupdateScript(record, 'add');
    await runNsupdate(script);
  }

  async removeRecord(record: Pick<DnsRecordSpec, 'fqdn' | 'recordType'>): Promise<void> {
    this.assertConfigured();
    const script = buildNsupdateScript(
      {
        serverId: 'remove',
        fqdn: record.fqdn,
        recordType: record.recordType,
        target: '0.0.0.0',
      },
      'delete',
    );
    await runNsupdate(script);
  }
}
