'use strict';

const { execFile: execFileCb } = require('child_process');
const fs = require('fs');
const path = require('path');
const {
  getBindHostCandidates,
  isTailscaleIPv4,
  isTailscaleIPv6,
} = require('./transport-security');

const TS_DNS_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*\.ts\.net$/;
const TAILSCALE_STATUS_TIMEOUT_MS = 8000;

function defaultExecFile(file, args, options = {}) {
  return new Promise((resolve, reject) => {
    execFileCb(file, args, options, (err, stdout, stderr) => {
      if (err) {
        err.stdout = stdout;
        err.stderr = stderr;
        reject(err);
        return;
      }
      resolve({ stdout, stderr });
    });
  });
}

function normalizeTailscaleDnsName(value) {
  const dns = String(value || '').trim().replace(/\.+$/, '').toLowerCase();
  if (!dns || !TS_DNS_RE.test(dns)) return '';
  return dns;
}

function shellQuote(value) {
  const s = String(value || '');
  if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(s)) return s;
  return `'${s.replace(/'/g, `'\\''`)}'`;
}

function firstTailscaleIp(values) {
  const ips = Array.isArray(values) ? values.map(String) : [];
  return ips.find(isTailscaleIPv4) || ips.find(isTailscaleIPv6) || '';
}

function buildTailscaleSetup(status, options = {}) {
  const self = status?.Self || status?.self || {};
  const dnsName = normalizeTailscaleDnsName(self.DNSName || self.dnsName || self.dns_name || '');
  if (!dnsName) {
    return {
      installed: true,
      available: false,
      error_code: 'tailscale_dns_missing',
      error: 'Tailscale is available, but MagicDNS did not report a usable *.ts.net name.',
    };
  }

  const candidates = getBindHostCandidates(options.interfaces);
  const host = firstTailscaleIp(self.TailscaleIPs || self.tailscaleIPs || self.tailscale_ips)
    || candidates.find((item) => item.kind === 'tailscale')?.value
    || '';
  if (!host) {
    return {
      installed: true,
      available: false,
      dns_name: dnsName,
      error_code: 'tailscale_ip_missing',
      error: 'Tailscale is available, but no 100.x or fd7a:115c:a1e0:: address was found.',
    };
  }

  const configDir = path.resolve(options.configDir || path.join(process.env.HOME || process.cwd(), '.walle', 'data'));
  const port = Number.isFinite(Number(options.port)) ? Number(options.port) : 3456;
  const certDir = path.join(configDir, 'tailscale-certs');
  const certPath = path.join(certDir, `${dnsName}.crt`);
  const keyPath = path.join(certDir, `${dnsName}.key`);
  const origin = `https://${dnsName}:${port}`;
  const certCommand = ['tailscale', 'cert', '--cert-file', certPath, '--key-file', keyPath, dnsName];
  const fsImpl = options.fs || fs;

  return {
    installed: true,
    available: true,
    // Authoritative tailnet liveness for connection-health: BackendState ('Running' = up,
    // 'Stopped'/'NeedsLogin'/'Starting' = not connected) + the human-readable Health
    // reasons. More reliable than Self.Online (which can be false for the local node even
    // when healthy). Absent backend_state → treated as up (old/odd `tailscale status`).
    backend_state: status?.BackendState || status?.backend_state || '',
    health: Array.isArray(status?.Health) ? status.Health.slice(0, 4) : [],
    dns_name: dnsName,
    host,
    origin,
    mobile_url: `${origin}/m/`,
    cert_path: certPath,
    key_path: keyPath,
    cert_exists: !!fsImpl.existsSync?.(certPath),
    key_exists: !!fsImpl.existsSync?.(keyPath),
    cert_command: certCommand,
    cert_command_display: certCommand.map(shellQuote).join(' '),
  };
}

async function detectTailscaleSetup(options = {}) {
  const execFile = options.execFile || defaultExecFile;
  try {
    const result = await execFile('tailscale', ['status', '--json'], {
      encoding: 'utf8',
      timeout: options.timeoutMs || TAILSCALE_STATUS_TIMEOUT_MS,
      maxBuffer: 2 * 1024 * 1024,
    });
    const stdout = typeof result === 'string' ? result : result?.stdout;
    let parsed;
    try {
      parsed = JSON.parse(String(stdout || ''));
    } catch {
      return {
        installed: true,
        available: false,
        error_code: 'tailscale_status_invalid',
        error: 'tailscale status --json did not return valid JSON.',
      };
    }
    return buildTailscaleSetup(parsed, options);
  } catch (err) {
    if (err && err.code === 'ENOENT') {
      return {
        installed: false,
        available: false,
        error_code: 'tailscale_cli_missing',
        error: 'Tailscale CLI is not installed or not on PATH.',
      };
    }
    return {
      installed: true,
      available: false,
      error_code: 'tailscale_status_failed',
      error: String(err?.stderr || err?.message || err || 'tailscale status failed').slice(0, 500),
    };
  }
}

async function generateTailscaleCert(setup, options = {}) {
  if (!setup || !setup.available || !setup.cert_path || !setup.key_path || !setup.dns_name) {
    return { ok: false, error_code: 'tailscale_setup_unavailable', error: 'Tailscale setup details are not available.' };
  }
  const execFile = options.execFile || defaultExecFile;
  const fsImpl = options.fs || fs;
  fsImpl.mkdirSync(path.dirname(setup.cert_path), { recursive: true, mode: 0o700 });
  try {
    fsImpl.chmodSync(path.dirname(setup.cert_path), 0o700);
  } catch {}

  try {
    await execFile('tailscale', setup.cert_command.slice(1), {
      encoding: 'utf8',
      timeout: options.timeoutMs || 60 * 1000,
      maxBuffer: 2 * 1024 * 1024,
    });
  } catch (err) {
    return {
      ok: false,
      error_code: err?.code === 'ENOENT' ? 'tailscale_cli_missing' : 'tailscale_cert_failed',
      error: String(err?.stderr || err?.message || err || 'tailscale cert failed').slice(0, 500),
    };
  }

  if (!fsImpl.existsSync(setup.cert_path) || !fsImpl.existsSync(setup.key_path)) {
    return {
      ok: false,
      error_code: 'tailscale_cert_missing_outputs',
      error: 'tailscale cert completed but did not create both certificate files.',
    };
  }
  try { fsImpl.chmodSync(setup.key_path, 0o600); } catch {}
  return {
    ok: true,
    cert_path: setup.cert_path,
    key_path: setup.key_path,
  };
}

module.exports = {
  buildTailscaleSetup,
  detectTailscaleSetup,
  generateTailscaleCert,
  normalizeTailscaleDnsName,
};
