import { createHash } from "node:crypto";
import { loadRemoteConfig, remoteReady, selectHosts } from "./remote-build.mjs";
import { probeMs, remoteTransport, shq } from "./remote-ssh.mjs";
import { SEAT_PROXY_PORT_BASE, SEAT_PROXY_PORT_SPAN } from "./seat-constants.mjs";
import { identityMatchesSeat, parseSeatJson } from "./seat-identity.mjs";
import { buildSeatResiduePreamble } from "./seat-runtime.mjs";

export function deriveSeatProxyPort(seatId) {
  const hash = createHash("sha256").update(seatId).digest();
  const offset = hash.readUInt32BE(0) % SEAT_PROXY_PORT_SPAN;
  return SEAT_PROXY_PORT_BASE + offset;
}

export function buildHostProbeScript(seatId) {
  const unit = `agent-seat-${seatId}.scope`;
  const socket = `"$HOME/.local/state/overdeck/seats/${seatId}/tmux.sock"`;
  const seatJson = `"$HOME/seats/${seatId}/seat.json"`;
  return [
    "set -euo pipefail",
    `seat_id=${shq(seatId)}`,
    `unit=${shq(unit)}`,
    `socket=${socket}`,
    `seat_json=${seatJson}`,
    buildSeatResiduePreamble(seatId),
    "_seat_collect_residue",
    "reachable=1",
    "scope=0",
    "socket_live=0",
    "seat_present=0",
    "seat_json_body=",
    "if [ -f \"$seat_json\" ]; then seat_present=1; seat_json_body=$(cat \"$seat_json\"); fi",
    "if systemctl --user is-active --quiet \"$unit\"; then scope=1; fi",
    "if [ -S \"$socket\" ] && tmux -S \"$socket\" list-sessions >/dev/null 2>&1; then socket_live=1; fi",
    "printf 'reachable=%s scope=%s socket=%s present=%s residue=%s\\n' \"$reachable\" \"$scope\" \"$socket_live\" \"$seat_present\" \"${residue_list:-}\"",
    "if [ \"$seat_present\" = 1 ]; then printf '%s\\n' \"$seat_json_body\"; fi",
  ].join("\n");
}

export function parseHostProbe(stdout) {
  const lines = String(stdout || "").split("\n");
  const header = lines.find((l) => l.startsWith("reachable="));
  if (!header) return { ok: false, reason: "probe-parse" };
  const parts = Object.fromEntries(header.split(" ").map((p) => p.split("=")));
  const seatJsonLine = lines.slice(lines.indexOf(header) + 1).join("\n").trim();
  let seat = null;
  if (parts.present === "1" && seatJsonLine) {
    const parsed = parseSeatJson(seatJsonLine);
    if (!parsed.ok) return { ok: false, reason: `seat-json-${parsed.reason}` };
    seat = parsed.seat;
  }
  return {
    ok: true,
    reachable: parts.reachable === "1",
    scopeActive: parts.scope === "1",
    socketLive: parts.socket === "1",
    seatPresent: parts.present === "1",
    residue: parts.residue ? parts.residue.split(",").filter(Boolean) : [],
    seat,
  };
}

export function classifyRuntimeState(probe) {
  if (!probe.ok) return { state: "unreachable", corrupt: false };
  if (!probe.reachable) return { state: "unreachable", corrupt: false };
  if (probe.residue?.length) return { state: "corrupt", corrupt: true };
  const scope = probe.scopeActive;
  const socket = probe.socketLive;
  if (scope && socket) return { state: "running", corrupt: false };
  if (!scope && !socket) return { state: "absent", corrupt: false };
  return { state: "corrupt", corrupt: true };
}

export function decideSeatHost({
  identity,
  requestedHost,
  probes,
  hosts,
  counts = {},
  cap = 6,
}) {
  const valid = [];
  const corrupt = [];
  const mismatched = [];
  for (const host of hosts) {
    const probe = probes[host];
    if (!probe?.ok || !probe.reachable) continue;
    if (!probe.seatPresent) continue;
    if (!identityMatchesSeat(identity, probe.seat)) {
      mismatched.push(host);
      continue;
    }
    const runtime = classifyRuntimeState(probe);
    if (runtime.corrupt) corrupt.push(host);
    else valid.push({ host, runtime: runtime.state, probe });
  }
  const reachableHosts = hosts.filter((h) => probes[h]?.ok && probes[h].reachable);
  const unreachable = hosts.filter((h) => !probes[h]?.ok || !probes[h].reachable);
  const withSeat = hosts.filter((h) => probes[h]?.seatPresent);
  if (mismatched.length) return { ok: false, reason: "identity-mismatch", hosts: mismatched };
  if (corrupt.length) return { ok: false, reason: "runtime-corrupt", hosts: corrupt };
  if (valid.length > 1) return { ok: false, reason: "duplicate-seat", hosts: valid.map((v) => v.host) };
  if (valid.length === 1) {
    const chosen = valid[0].host;
    if (requestedHost !== "auto" && requestedHost !== chosen) {
      return { ok: false, reason: "host-mismatch", expected: chosen, requested: requestedHost };
    }
    const runtime = valid[0].runtime;
    const action = runtime === "running" ? "attach" : "restart";
    return { ok: true, action, host: chosen, runtime };
  }
  if (withSeat.length) return { ok: false, reason: "hidden-seat", hosts: withSeat };
  if (unreachable.length === hosts.length) return { ok: false, reason: "all-hosts-unreachable" };
  if (unreachable.length > 0) return { ok: false, reason: "partial-unreachable-hidden-duplicate", unreachable };
  if (requestedHost !== "auto") {
    if (!reachableHosts.includes(requestedHost)) return { ok: false, reason: "requested-host-unreachable", host: requestedHost };
    return { ok: true, action: "create", host: requestedHost };
  }
  const ordered = selectHosts(reachableHosts, "", counts, cap);
  if (!ordered.length) return { ok: false, reason: "no-eligible-host" };
  return { ok: true, action: "create", host: ordered[0] };
}

export function probeSeatHosts(cfg, seatId, stateDir, log = () => {}, deps = {}) {
  const transport = deps.remoteTransport ?? remoteTransport;
  const ready = deps.remoteReady ?? remoteReady;
  const script = buildHostProbeScript(seatId);
  const probes = {};
  for (const host of cfg.hosts) {
    const hostCfg = { ...cfg, host };
    if (!ready(hostCfg, stateDir, log)) {
      probes[host] = { ok: false, reason: "not-ready", reachable: false };
      continue;
    }
    const result = transport(hostCfg, script, { timeoutSec: cfg.ssh_probe_timeout_sec ?? 15 }, deps);
    if (result.status !== 0) {
      probes[host] = { ok: false, reason: "ssh-failed", reachable: false };
      continue;
    }
    const parsed = parseHostProbe(result.stdout);
    if (!parsed.ok) {
      probes[host] = { ok: false, reason: parsed.reason, reachable: false };
      continue;
    }
    probes[host] = parsed;
  }
  return probes;
}

export function loadSeatRemoteConfig(deps = {}) {
  const load = deps.loadRemoteConfig ?? loadRemoteConfig;
  return load();
}

export function defaultSeatStateDir(home) {
  return `${home}/.local/state/overdeck/seat-remote`;
}
