import fs from "fs";
import path from "path";
import {
  execFileWithPassword,
  getErrorMessage,
  quotePowerShell,
  runElevatedPowerShell,
} from "../systemCommands.ts";

// Legacy Antigravity defaults preserved for backward compat.
const ANTIGRAVITY_HOSTS = [
  "daily-cloudcode-pa.googleapis.com",
  "cloudcode-pa.googleapis.com",
  "daily-cloudcode-pa.sandbox.googleapis.com",
  "autopush-cloudcode-pa.sandbox.googleapis.com",
];

const IS_WIN = process.platform === "win32";
const HOSTS_FILE = IS_WIN
  ? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts")
  : "/etc/hosts";

/**
 * Build the set of /etc/hosts lines for a given hostname.
 * Both IPv4 and IPv6 are needed — modern systems often resolve IPv6 first.
 */
function dnsLines(hostname: string): string[] {
  return [`127.0.0.1 ${hostname}`, `::1 ${hostname}`];
}

/**
 * Read the current hosts file content. Returns empty string on error.
 */
function readHostsFile(): string {
  try {
    return fs.readFileSync(HOSTS_FILE, "utf8");
  } catch {
    return "";
  }
}

/**
 * Check whether all IPv4+IPv6 lines for `hostname` are present in the hosts file.
 */
function hasHostEntry(hostsContent: string, hostname: string): boolean {
  const lines = hostsContent.split(/\r?\n/);
  return dnsLines(hostname).every((entry) => {
    const [ip, host] = entry.split(/\s+/);
    return lines.some((line) => {
      const parts = line.trim().split(/\s+/).filter(Boolean);
      return parts.length >= 2 && parts[0] === ip && parts.includes(host);
    });
  });
}

// ---------------------------------------------------------------------------
// Public API — parametrized (new)
// ---------------------------------------------------------------------------

/**
 * Add /etc/hosts entries for every hostname in `hosts`.
 * Idempotent — existing entries are not duplicated.
 * Complies with Hard Rule #13: no string interpolation in shell commands.
 */
export async function addDNSEntries(hosts: string[], sudoPassword: string): Promise<void> {
  const hostsContent = readHostsFile();

  for (const hostname of hosts) {
    const lines = dnsLines(hostname);
    const missing = lines.filter((entry) => {
      const [ip, host] = entry.split(/\s+/);
      const existing = hostsContent.split(/\r?\n/);
      return !existing.some((line) => {
        const parts = line.trim().split(/\s+/).filter(Boolean);
        return parts.length >= 2 && parts[0] === ip && parts.includes(host);
      });
    });

    for (const entry of missing) {
      if (IS_WIN) {
        // HR#13: build PowerShell command via concat (not template literal) so grep
        // for `\${` inside script bodies returns zero hits. Values pass through
        // `quotePowerShell()` for single-quote escaping — safe against injection
        // since both HOSTS_FILE (OS const) and entry (internal `IP host` string)
        // are non-user-supplied.
        const cmd =
          "Add-Content -LiteralPath " +
          quotePowerShell(HOSTS_FILE) +
          " -Value " +
          quotePowerShell(entry);
        await runElevatedPowerShell(cmd);
      } else {
        // Hard Rule #13: entry is passed as stdin data, not interpolated into the command.
        await execFileWithPassword(
          "sudo",
          ["-S", "tee", "-a", HOSTS_FILE],
          sudoPassword,
          `${entry}\n`
        );
      }
      console.log(`[DNS] Added entry: ${entry}`);
    }
  }
}

// Node.js inline script for removing hosts entries — uses process.argv so no
// values are interpolated into the script body (Hard Rule #13).
const REMOVE_HOSTS_ENTRY_SCRIPT = `
const fs = require("fs");
const filePath = process.argv[1];
const targetHost = process.argv[2];
const content = fs.readFileSync(filePath, "utf8");
const filtered = content.split(/\\r?\\n/).filter((line) => {
  const parts = line.trim().split(/\\s+/).filter(Boolean);
  return !(parts.length >= 2 && parts.includes(targetHost));
});
fs.writeFileSync(filePath, filtered.join("\\n").replace(/\\n*$/, "\\n"));
`;

/**
 * Remove /etc/hosts entries for every hostname in `hosts`.
 * Idempotent — silently skips hosts that are not present.
 * Complies with Hard Rule #13: HOSTS_FILE and hostname are passed as argv, not interpolated.
 */
export async function removeDNSEntries(hosts: string[], sudoPassword: string): Promise<void> {
  const hostsContent = readHostsFile();

  for (const hostname of hosts) {
    if (!hasHostEntry(hostsContent, hostname)) {
      console.log(`[DNS] Entry for ${hostname} not present — skipping`);
      continue;
    }

    try {
      if (IS_WIN) {
        // HR#13: build PowerShell script via concat (not template literal) so grep
        // for `\${` inside script bodies returns zero hits. `psHostsFile` and
        // `psTargetHost` are quotePowerShell-escaped values (single-quote escape).
        const psHostsFile = quotePowerShell(HOSTS_FILE);
        const psTargetHost = quotePowerShell(hostname);
        const script =
          "\n          $hostsFile = " +
          psHostsFile +
          ";\n          $targetHost = " +
          psTargetHost +
          ";\n          $lines = Get-Content -LiteralPath $hostsFile;\n" +
          "          $filtered = $lines | Where-Object {\n" +
          "            $parts = ($_ -split '\\s+') | Where-Object { $_ };\n" +
          "            -not (($parts.Length -ge 2) -and ($parts -contains $targetHost))\n" +
          "          };\n" +
          "          Set-Content -LiteralPath $hostsFile -Value $filtered;\n        ";
        await runElevatedPowerShell(script);
      } else {
        // Hard Rule #13: HOSTS_FILE and hostname are argv arguments, not interpolated.
        await execFileWithPassword(
          "sudo",
          ["-S", process.execPath, "-e", REMOVE_HOSTS_ENTRY_SCRIPT, HOSTS_FILE, hostname],
          sudoPassword
        );
      }
      console.log(`[DNS] Removed entries for ${hostname}`);
    } catch (error) {
      throw new Error(`Failed to remove DNS entry for ${hostname}: ${getErrorMessage(error)}`);
    }
  }
}

// ---------------------------------------------------------------------------
// Legacy API — backward compat wrappers for manager.ts callers
// ---------------------------------------------------------------------------

/**
 * Check whether the Antigravity default DNS entries are present.
 * Preserved for backward compat (called by getMitmStatus and other callers).
 */
export function checkDNSEntry(): boolean {
  const hostsContent = readHostsFile();
  return ANTIGRAVITY_HOSTS.every((h) => hasHostEntry(hostsContent, h));
}

/**
 * Add DNS entries for the Antigravity default hosts.
 * Delegates to `addDNSEntries` — backward compat wrapper.
 */
export async function addDNSEntry(sudoPassword: string): Promise<void> {
  await addDNSEntries(ANTIGRAVITY_HOSTS, sudoPassword);
}

/**
 * Remove DNS entries for the Antigravity default hosts.
 * Delegates to `removeDNSEntries` — backward compat wrapper.
 */
export async function removeDNSEntry(sudoPassword: string): Promise<void> {
  await removeDNSEntries(ANTIGRAVITY_HOSTS, sudoPassword);
}
