import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { homedir } from "node:os";
import { SEAT_ACCOUNT_ALLOWLIST, SEAT_BINDING_REL } from "./seat-constants.mjs";
import { seatProfileHome } from "./seat-identity.mjs";

const SYSTRAY_RUNTIME_REL = [".local", "state", "overdeck", "systray", "runtime"];
const SYSTRAY_COMPAT_REL = [".systray-ai"];
const SYSTRAY_ACCOUNTS_REL = [...SYSTRAY_RUNTIME_REL, "accounts"];

const PROVIDER_CREDENTIAL_KEYS = new Set([
  "anthropicApiKey",
  "openaiApiKey",
  "apiKey",
  "oauthToken",
  "accessToken",
  "refreshToken",
  "codex",
  "anthropic",
]);

const SEAT_CLAUDE_CONFIG_KEYS = {
  hasCompletedOnboarding: "boolean",
  lastOnboardingVersion: "string",
  migrationVersion: "number",
  installMethod: "string",
};

function assertNoCredentialMaterialInClaudeJson(obj) {
  const walk = (value) => {
    if (!value || typeof value !== "object") return;
    for (const key of Object.keys(value)) {
      if (PROVIDER_CREDENTIAL_KEYS.has(key) || key.toLowerCase().includes("oauth") || key.toLowerCase().includes("apikey")) {
        throw new Error(`credential-material:${key}`);
      }
      walk(value[key]);
    }
  };
  walk(obj);
}

export function credentialsFingerprint(payload) {
  return createHash("sha256").update(JSON.stringify(payload)).digest("hex");
}

export function buildSeatClaudeConfig(raw, deps = {}) {
  const allowlist = deps.allowlist ?? SEAT_CLAUDE_CONFIG_KEYS;
  const data = JSON.parse(String(raw));
  if (!data || typeof data !== "object" || Array.isArray(data)) {
    throw new Error("seat-claude-config:invalid-root");
  }
  const out = { autoUpdates: false };
  for (const [key, expectedType] of Object.entries(allowlist)) {
    if (!(key in data)) continue;
    const value = data[key];
    if (typeof value !== expectedType) {
      throw new Error(`seat-claude-config:invalid-type:${key}`);
    }
    out[key] = value;
  }
  assertNoCredentialMaterialInClaudeJson(out);
  return out;
}

export function validateAllowlistEntry(name) {
  if (!SEAT_ACCOUNT_ALLOWLIST.includes(name)) return false;
  return true;
}

export function buildLaptopBindingPath(home, accountSlug) {
  return join(home, SEAT_BINDING_REL, `${accountSlug}.json`);
}

export function readLaptopBinding(home, accountSlug) {
  const path = buildLaptopBindingPath(home, accountSlug);
  if (!existsSync(path)) return null;
  try {
    return JSON.parse(readFileSync(path, "utf8"));
  } catch {
    return { corrupt: true };
  }
}

export function writeLaptopBindingAtomic(home, accountSlug, binding) {
  const path = buildLaptopBindingPath(home, accountSlug);
  mkdirSync(dirname(path), { recursive: true });
  const tmp = `${path}.tmp.${process.pid}`;
  writeFileSync(tmp, `${JSON.stringify(binding, null, 2)}\n`, { mode: 0o600 });
  renameSync(tmp, path);
}

export function shouldFailClosedLocalClaudex(home, accountSlug) {
  const binding = readLaptopBinding(home, accountSlug);
  return Boolean(binding && !binding.corrupt);
}

export function buildBindingRecord({ accountSlug, seatId, host, model, credentialFingerprint }) {
  return {
    account: accountSlug,
    seatId,
    host,
    model,
    credentialFingerprint,
    boundAt: new Date().toISOString(),
  };
}

export function resolveCodexAuthPath(home, accountSlug, deps = {}) {
  const exists = deps.existsSync ?? existsSync;
  const candidates = [
    join(home, ...SYSTRAY_ACCOUNTS_REL, accountSlug, "CODEX_HOME", "auth.json"),
    join(home, ...SYSTRAY_COMPAT_REL, "accounts", accountSlug, "CODEX_HOME", "auth.json"),
    join(home, ".codex-tray", "accounts", accountSlug, "CODEX_HOME", "auth.json"),
  ];
  for (const path of candidates) {
    if (exists(path)) return path;
  }
  return null;
}

export function resolveLaptopClaudeAccountHome(home, accountSlug, deps = {}) {
  const exists = deps.existsSync ?? existsSync;
  const candidates = [
    join(home, ...SYSTRAY_RUNTIME_REL, "claude-accounts", accountSlug, "CLAUDE_HOME"),
    join(home, ...SYSTRAY_COMPAT_REL, "claude-accounts", accountSlug, "CLAUDE_HOME"),
  ];
  for (const path of candidates) {
    if (exists(path)) return path;
  }
  return null;
}

export function collectAllowlistedPaths(accountHome, deps = {}) {
  const exists = deps.existsSync ?? existsSync;
  const lstat = deps.lstatSync;
  const entries = [];
  for (const name of SEAT_ACCOUNT_ALLOWLIST) {
    const path = join(accountHome, name);
    if (!exists(path)) continue;
    entries.push({ name, path });
  }
  return entries;
}

export function seatRemoteProfilePaths(home, accountSlug, seatId) {
  const profile = seatProfileHome(home, accountSlug, seatId);
  return {
    profile,
    secureStorage: join(profile, "secure-storage"),
  };
}

export function parseCodexAuthFile(path, deps = {}) {
  const read = deps.readFileSync ?? readFileSync;
  try {
    const raw = read(path, "utf8");
    const data = JSON.parse(String(raw));
    if (!data || typeof data !== "object") return { ok: false, reason: "codex-auth-invalid" };
    const hasToken = Boolean(
      data.access_token
      || data.api_key
      || data.apiKey
      || data.tokens
      || data.openai?.api_key,
    );
    if (!hasToken) return { ok: false, reason: "codex-auth-empty" };
    return { ok: true, payload: data, raw: String(raw) };
  } catch {
    return { ok: false, reason: "codex-auth-parse" };
  }
}

export function defaultHome() {
  return homedir();
}
