import { createHash } from "node:crypto";
import { shq } from "./remote-ssh.mjs";
import { SEAT_PROXY_BASE, SEAT_PROXY_USER } from "./seat-constants.mjs";
import { implementerUsernameForSeat } from "./seat-implementer-identity.mjs";

export { implementerUsernameForSeat };

const IFACE_MAX = 15;
export const SEAT_NETNS_MARKER = "netns-owner.json";
export const NETNS_MARKER_READ_OWNER_PY =
  "import json,sys; print(json.load(open(sys.argv[1])).get('seatId',''))";

export function netnsMarkerSudoTestF(pathRef) {
  return `sudo -n test -f ${pathRef}`;
}

export function netnsMarkerSudoReadOwner(pathRef) {
  return `sudo -n python3 -c ${shq(NETNS_MARKER_READ_OWNER_PY)} ${pathRef}`;
}

export function deriveNetnsLayout(seatId) {
  const digest = createHash("sha256").update(seatId).digest();
  const hash8 = digest.toString("hex").slice(0, 8);
  const netnsName = `overdeck-seat-${seatId}`;
  const netnsPath = `/var/run/netns/${netnsName}`;
  const vethNs = `ods${hash8}`.slice(0, IFACE_MAX);
  const vethHost = `ods${hash8}h`.slice(0, IFACE_MAX);
  const hi = digest[0] % 254 + 1;
  const base = digest[1] & 0xfc;
  const hostIp = `169.254.${hi}.${base + 1}`;
  const nsIp = `169.254.${hi}.${base + 2}`;
  const subnet = `169.254.${hi}.${base}/30`;
  const nftTable = `od_seat_${hash8}`;
  const natTable = `od_seat_nat_${hash8}`;
  const markerPath = `${SEAT_PROXY_BASE}/${seatId}/${SEAT_NETNS_MARKER}`;
  return {
    seatId,
    hash8,
    netnsName,
    netnsPath,
    vethHost,
    vethNs,
    hostIp,
    nsIp,
    hostPrefix: `${hostIp}/30`,
    nsPrefix: `${nsIp}/30`,
    subnet,
    nftTable,
    natTable,
    markerPath,
  };
}

export function netnsIfaceNamesWithinLimit(layout) {
  return layout.vethHost.length <= IFACE_MAX && layout.vethNs.length <= IFACE_MAX;
}

export function buildNetnsOwnerMarkerContent(layout, port) {
  return `${JSON.stringify({
    seatId: layout.seatId,
    hash8: layout.hash8,
    netnsName: layout.netnsName,
    netnsPath: layout.netnsPath,
    vethHost: layout.vethHost,
    vethNs: layout.vethNs,
    subnet: layout.subnet,
    hostPrefix: layout.hostPrefix,
    nsPrefix: layout.nsPrefix,
    nftTable: layout.nftTable,
    natTable: layout.natTable,
    port,
  }, null, 2)}\n`;
}

export function nftTableExistsAwk(family, nameVar) {
  return `-v family=${shq(family)} -v name=\"$${nameVar}\" '$1=="table" && $2==family && $3==name'`;
}


export function buildNsNftImplementerProxyFragment(layout, port, implementerUid = "$implementer_uid") {
  return `ip daddr ${layout.nsIp} tcp dport ${port} meta skuid ${implementerUid} accept`;
}

export function buildNsNftExpectedRules(layout, port) {
  return [
    `table ip ${layout.nftTable} {`,
    `chain output {`,
    `type filter hook output priority filter; policy drop;`,
    `ip daddr 127.0.0.0/8 accept`,
  ];
}

export function buildNsNftProxySkuidFragment(proxyUid = "$proxy_uid") {
  return `meta skuid ${proxyUid} accept`;
}

export function buildHostNatExpectedRules(layout) {
  return [
    `table ip ${layout.natTable} {`,
    `chain forward {`,
    `type filter hook forward priority filter; policy accept;`,
    `iifname "${layout.vethHost}" ip saddr ${layout.subnet} accept`,
    `oifname "${layout.vethHost}" ct state established,related accept`,
    `iifname "${layout.vethHost}" drop`,
    `chain postrouting {`,
    `type nat hook postrouting priority srcnat; policy accept;`,
    `ip saddr ${layout.subnet} oifname != "${layout.vethHost}" masquerade`,
  ];
}

export function buildNftRulesetVerifyScript({ rules, listCmd, label, shellRuleExprs = [] }) {
  const quoted = rules.map((r) => shq(r)).join(" ");
  const dynamic = shellRuleExprs.length
    ? [`rules+=(${shellRuleExprs.join(" ")})`]
    : [];
  return [
    `rules=(${quoted})`,
    ...dynamic,
    `table_out=$(${listCmd} 2>/dev/null || true)`,
    "for rule in \"${rules[@]}\"; do",
    `  echo "$table_out" | grep -Fq -- "$rule" || { echo ${shq(`${label}-drift`)}; exit 1; }`,
    "done",
  ].join("\n");
}

export function buildNsNftApplyScript(layout, { port, implementerUser = implementerUsernameForSeat(layout.seatId) }) {
  const nftExistsAwk = nftTableExistsAwk("ip", "nft_table");
  const expected = buildNsNftExpectedRules(layout, port);
  return [
    `implementer_user=${shq(implementerUser)}`,
    `implementer_uid=$(id -u "$implementer_user" 2>/dev/null || { echo netns-implementer-user-missing; exit 1; })`,
    `port=${port}`,
    `proxy_uid=$(id -u ${SEAT_PROXY_USER} 2>/dev/null || { echo netns-proxy-user-missing; exit 1; })`,
    "if ! sudo -n ip netns exec \"$netns_name\" nft list tables 2>/dev/null | awk " + nftExistsAwk + " | grep -q .; then",
    "  sudo -n ip netns exec \"$netns_name\" nft add table ip \"$nft_table\"",
    `  sudo -n ip netns exec "$netns_name" nft add chain ip $nft_table output \\{ type filter hook output priority 0\\; policy drop\\; \\}`,
    `  sudo -n ip netns exec "$netns_name" nft add rule ip "$nft_table" output ip daddr 127.0.0.0/8 accept`,
    `  sudo -n ip netns exec "$netns_name" nft add rule ip "$nft_table" output ip daddr "$ns_ip" tcp dport "$port" meta skuid "$implementer_uid" accept`,
    `  sudo -n ip netns exec "$netns_name" nft add rule ip "$nft_table" output meta skuid "$proxy_uid" accept`,
    "fi",
    buildNftRulesetVerifyScript({
      rules: expected,
      shellRuleExprs: [
        `"ip daddr $ns_ip tcp dport $port meta skuid $implementer_uid accept"`,
        `"meta skuid $proxy_uid accept"`,
      ],
      listCmd: "sudo -n ip netns exec \"$netns_name\" nft list table ip \"$nft_table\"",
      label: "netns-nft",
    }),
  ].join("\n");
}

export function buildHostNatApplyScript(layout) {
  const natExistsAwk = nftTableExistsAwk("ip", "nat_table");
  const expected = buildHostNatExpectedRules(layout);
  return [
    "if ! sudo -n nft list tables 2>/dev/null | awk " + natExistsAwk + " | grep -q .; then",
    "  sudo -n nft add table ip \"$nat_table\"",
    `  sudo -n nft add chain ip $nat_table forward \\{ type filter hook forward priority 0\\; policy accept\\; \\}`,
    `  sudo -n nft add rule ip "$nat_table" forward iifname "$veth_host" ip saddr "$subnet" accept`,
    `  sudo -n nft add rule ip "$nat_table" forward oifname "$veth_host" ct state established,related accept`,
    `  sudo -n nft add rule ip "$nat_table" forward iifname "$veth_host" drop`,
    `  sudo -n nft add chain ip $nat_table postrouting \\{ type nat hook postrouting priority srcnat\\; policy accept\\; \\}`,
    `  sudo -n nft add rule ip "$nat_table" postrouting ip saddr "$subnet" oifname != "$veth_host" masquerade`,
    "fi",
    buildNftRulesetVerifyScript({
      rules: expected,
      listCmd: "sudo -n nft list table ip \"$nat_table\"",
      label: "host-nat",
    }),
  ].join("\n");
}

export function buildNetnsCollisionGuardScript(layout, port) {
  return [
    "set -euo pipefail",
    `seat_id=${shq(layout.seatId)}`,
    `marker=${shq(layout.markerPath)}`,
    `veth_host=${shq(layout.vethHost)}`,
    `netns=${shq(layout.netnsPath)}`,
    `netns_name=${shq(layout.netnsName)}`,
    `port=${port}`,
    "read_marker() {",
    `  if ! ${netnsMarkerSudoTestF('"$1"')}; then echo; return 0; fi`,
    `  ${netnsMarkerSudoReadOwner('"$1"')} 2>/dev/null || true`,
    "}",
    "if ip link show \"$veth_host\" >/dev/null 2>&1; then",
    "  owner=$(read_marker \"$marker\")",
    "  if [ \"$owner\" != \"$seat_id\" ]; then echo netns-veth-collision; exit 1; fi",
    "fi",
    "if [ -e \"$netns\" ]; then",
    "  owner=$(read_marker \"$marker\")",
    "  if [ \"$owner\" != \"$seat_id\" ]; then echo netns-name-collision; exit 1; fi",
    "fi",
    "if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -q \":${port}$\"; then",
    "  owner=$(read_marker \"$marker\")",
    "  if [ \"$owner\" != \"$seat_id\" ]; then echo netns-port-collision; exit 1; fi",
    "fi",
  ].join("\n");
}

export function buildNetnsOwnerMarkerWriteScript(layout, port) {
  const content = buildNetnsOwnerMarkerContent(layout, port);
  return [
    "set -euo pipefail",
    `marker=${shq(layout.markerPath)}`,
    `dir=${shq(`${SEAT_PROXY_BASE}/${layout.seatId}`)}`,
    "sudo -n install -d -o root -g overdeck-seat-proxy -m 0710 \"$dir\"",
    "tmp=$(mktemp)",
    `cat >"$tmp" <<'OD_NETNS_OWNER'\n${content}OD_NETNS_OWNER`,
    "sudo -n install -o root -g root -m 0644 \"$tmp\" \"$marker\"",
    "rm -f \"$tmp\"",
    `read_owner=$(${netnsMarkerSudoReadOwner('"$marker"')})`,
    `[ "$read_owner" = ${shq(layout.seatId)} ] || { echo netns-marker-readback; exit 1; }`,
  ].join("\n");
}

export function buildNetnsOwnerMarkerVerifyScript(layout) {
  return [
    "set -euo pipefail",
    `marker=${shq(layout.markerPath)}`,
    `${netnsMarkerSudoTestF('"$marker"')} || { echo netns-marker-missing; exit 1; }`,
    `read_owner=$(${netnsMarkerSudoReadOwner('"$marker"')})`,
    `[ "$read_owner" = ${shq(layout.seatId)} ] || { echo netns-marker-drift; exit 1; }`,
  ].join("\n");
}

export function buildNetnsOwnerMarkerDeleteScript(layout) {
  return [
    "set -euo pipefail",
    `marker=${shq(layout.markerPath)}`,
    `if ${netnsMarkerSudoTestF('"$marker"')}; then`,
    `  read_owner=$(${netnsMarkerSudoReadOwner('"$marker"')})`,
    `  [ "$read_owner" = ${shq(layout.seatId)} ] || { echo netns-marker-collision; exit 1; }`,
    "  sudo -n rm -f \"$marker\"",
    "fi",
  ].join("\n");
}
