import { runCommandWithTimeout } from "../process/exec.js";

export type GatewayBonjourBeacon = {
  instanceName: string;
  displayName?: string;
  host?: string;
  port?: number;
  lanHost?: string;
  tailnetDns?: string;
  bridgePort?: number;
  gatewayPort?: number;
  sshPort?: number;
  cliPath?: string;
  txt?: Record<string, string>;
};

export type GatewayBonjourDiscoverOpts = {
  timeoutMs?: number;
};

const DEFAULT_TIMEOUT_MS = 2000;

function parseIntOrNull(value: string | undefined): number | undefined {
  if (!value) return undefined;
  const parsed = Number.parseInt(value, 10);
  return Number.isFinite(parsed) ? parsed : undefined;
}

function parseTxtTokens(tokens: string[]): Record<string, string> {
  const txt: Record<string, string> = {};
  for (const token of tokens) {
    const idx = token.indexOf("=");
    if (idx <= 0) continue;
    const key = token.slice(0, idx).trim();
    const value = token.slice(idx + 1).trim();
    if (!key) continue;
    txt[key] = value;
  }
  return txt;
}

function parseDnsSdBrowse(stdout: string): string[] {
  const instances = new Set<string>();
  for (const raw of stdout.split("\n")) {
    const line = raw.trim();
    if (!line || !line.includes("_clawdis-bridge._tcp")) continue;
    if (!line.includes("Add")) continue;
    const match = line.match(/_clawdis-bridge\._tcp\.?\s+(.+)$/);
    if (match?.[1]) {
      instances.add(match[1].trim());
    }
  }
  return Array.from(instances.values());
}

function parseDnsSdResolve(
  stdout: string,
  instanceName: string,
): GatewayBonjourBeacon | null {
  const beacon: GatewayBonjourBeacon = { instanceName };
  let txt: Record<string, string> = {};
  for (const raw of stdout.split("\n")) {
    const line = raw.trim();
    if (!line) continue;

    if (line.includes("can be reached at")) {
      const match = line.match(/can be reached at\s+([^\s:]+):(\d+)/i);
      if (match?.[1]) {
        beacon.host = match[1].replace(/\.$/, "");
      }
      if (match?.[2]) {
        beacon.port = parseIntOrNull(match[2]);
      }
      continue;
    }

    if (line.startsWith("txt") || line.includes("txtvers=")) {
      const tokens = line.split(/\s+/).filter(Boolean);
      txt = parseTxtTokens(tokens);
    }
  }

  beacon.txt = Object.keys(txt).length ? txt : undefined;
  if (txt.displayName) beacon.displayName = txt.displayName;
  if (txt.lanHost) beacon.lanHost = txt.lanHost;
  if (txt.tailnetDns) beacon.tailnetDns = txt.tailnetDns;
  if (txt.cliPath) beacon.cliPath = txt.cliPath;
  beacon.bridgePort = parseIntOrNull(txt.bridgePort);
  beacon.gatewayPort = parseIntOrNull(txt.gatewayPort);
  beacon.sshPort = parseIntOrNull(txt.sshPort);

  if (!beacon.displayName) beacon.displayName = instanceName;
  return beacon;
}

async function discoverViaDnsSd(
  timeoutMs: number,
): Promise<GatewayBonjourBeacon[]> {
  const browse = await runCommandWithTimeout(
    ["dns-sd", "-B", "_clawdis-bridge._tcp", "local."],
    { timeoutMs },
  );
  const instances = parseDnsSdBrowse(browse.stdout);
  const results: GatewayBonjourBeacon[] = [];
  for (const instance of instances) {
    const resolved = await runCommandWithTimeout(
      ["dns-sd", "-L", instance, "_clawdis-bridge._tcp", "local."],
      { timeoutMs },
    );
    const parsed = parseDnsSdResolve(resolved.stdout, instance);
    if (parsed) results.push(parsed);
  }
  return results;
}

function parseAvahiBrowse(stdout: string): GatewayBonjourBeacon[] {
  const results: GatewayBonjourBeacon[] = [];
  let current: GatewayBonjourBeacon | null = null;

  for (const raw of stdout.split("\n")) {
    const line = raw.trimEnd();
    if (!line) continue;
    if (line.startsWith("=") && line.includes("_clawdis-bridge._tcp")) {
      if (current) results.push(current);
      const marker = " _clawdis-bridge._tcp";
      const idx = line.indexOf(marker);
      const left = idx >= 0 ? line.slice(0, idx).trim() : line;
      const parts = left.split(/\s+/);
      const instanceName = parts.length > 3 ? parts.slice(3).join(" ") : left;
      current = {
        instanceName,
        displayName: instanceName,
      };
      continue;
    }

    if (!current) continue;

    const trimmed = line.trim();
    if (trimmed.startsWith("hostname =")) {
      const match = trimmed.match(/hostname\s*=\s*\[([^\]]+)\]/);
      if (match?.[1]) current.host = match[1];
      continue;
    }

    if (trimmed.startsWith("port =")) {
      const match = trimmed.match(/port\s*=\s*\[(\d+)\]/);
      if (match?.[1]) current.port = parseIntOrNull(match[1]);
      continue;
    }

    if (trimmed.startsWith("txt =")) {
      const tokens = Array.from(trimmed.matchAll(/"([^"]*)"/g), (m) => m[1]);
      const txt = parseTxtTokens(tokens);
      current.txt = Object.keys(txt).length ? txt : undefined;
      if (txt.displayName) current.displayName = txt.displayName;
      if (txt.lanHost) current.lanHost = txt.lanHost;
      if (txt.tailnetDns) current.tailnetDns = txt.tailnetDns;
      if (txt.cliPath) current.cliPath = txt.cliPath;
      current.bridgePort = parseIntOrNull(txt.bridgePort);
      current.gatewayPort = parseIntOrNull(txt.gatewayPort);
      current.sshPort = parseIntOrNull(txt.sshPort);
    }
  }

  if (current) results.push(current);
  return results;
}

async function discoverViaAvahi(
  timeoutMs: number,
): Promise<GatewayBonjourBeacon[]> {
  const browse = await runCommandWithTimeout(
    ["avahi-browse", "-rt", "_clawdis-bridge._tcp"],
    { timeoutMs },
  );
  return parseAvahiBrowse(browse.stdout);
}

export async function discoverGatewayBeacons(
  opts: GatewayBonjourDiscoverOpts = {},
): Promise<GatewayBonjourBeacon[]> {
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
  try {
    if (process.platform === "darwin") {
      return await discoverViaDnsSd(timeoutMs);
    }
    if (process.platform === "linux") {
      return await discoverViaAvahi(timeoutMs);
    }
  } catch {
    return [];
  }
  return [];
}
