import { execFile } from 'child_process';

export interface TailscaleServeConfig {
  /** The gateway's local port (e.g. 18800). */
  localPort: number;
  /** Tailscale Serve HTTPS port (default 443). */
  servePort?: number;
  /** Custom hostname (default: machine hostname). */
  hostname?: string;
}

/** Execute a command with a timeout, returning stdout. */
function exec(cmd: string, args: string[], timeoutMs = 5000): Promise<string> {
  return new Promise((resolve, reject) => {
    execFile(cmd, args, { timeout: timeoutMs }, (error, stdout, stderr) => {
      if (error) {
        reject(new Error(`${cmd} ${args.join(' ')} failed: ${stderr || error.message}`));
      } else {
        resolve(stdout.trim());
      }
    });
  });
}

/**
 * Check if Tailscale CLI is available and the daemon is running.
 */
export async function isTailscaleAvailable(): Promise<{ available: boolean; version?: string; error?: string }> {
  try {
    const version = await exec('tailscale', ['version', '--short']);
    // Check if daemon is running by getting status
    await exec('tailscale', ['status', '--json']);
    return { available: true, version };
  } catch (err) {
    return { available: false, error: err instanceof Error ? err.message : String(err) };
  }
}

/**
 * Get the MagicDNS hostname from Tailscale status.
 */
async function getTailscaleHostname(): Promise<string | undefined> {
  try {
    const statusJson = await exec('tailscale', ['status', '--json']);
    const status = JSON.parse(statusJson) as { Self?: { DNSName?: string } };
    const dnsName = status.Self?.DNSName;
    // DNSName ends with a trailing dot — strip it
    return dnsName?.replace(/\.$/, '');
  } catch {
    return undefined;
  }
}

/**
 * Start Tailscale Serve to expose the local gateway port on the tailnet.
 *
 * Runs: `tailscale serve --bg --https=<servePort> http://127.0.0.1:<localPort>`
 */
export async function startTailscaleServe(config: TailscaleServeConfig): Promise<string | undefined> {
  const servePort = config.servePort ?? 443;
  const target = `http://127.0.0.1:${config.localPort}`;

  try {
    await exec('tailscale', ['serve', '--bg', `--https=${servePort}`, target]);
    const hostname = config.hostname ?? await getTailscaleHostname();
    if (hostname) {
      const url = servePort === 443 ? `https://${hostname}` : `https://${hostname}:${servePort}`;
      console.log(`Tailscale Serve: Flynn available at ${url}`);
      return url;
    }
    console.log('Tailscale Serve: started (could not determine hostname)');
    return undefined;
  } catch (err) {
    console.error('Failed to start Tailscale Serve:', err instanceof Error ? err.message : err);
    throw err;
  }
}

/**
 * Stop Tailscale Serve for the configured port.
 *
 * Runs: `tailscale serve --https=<servePort> off`
 */
export async function stopTailscaleServe(config: TailscaleServeConfig): Promise<void> {
  const servePort = config.servePort ?? 443;

  try {
    await exec('tailscale', ['serve', `--https=${servePort}`, 'off']);
    console.log('Tailscale Serve: stopped');
  } catch (err) {
    // Best effort — don't fail shutdown
    console.error('Failed to stop Tailscale Serve:', err instanceof Error ? err.message : err);
  }
}
