import { createHash } from "node:crypto";
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";

import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";

import { readCodexPoolConfig } from "./config";
import { capture, parseJsonOutput } from "./remote";
import { renderedCliResult, renderTable, textValue } from "./render";
import { codexPoolRuntimeTarget } from "./runtime-target";

interface UserEnsureOptions {
  email: string;
  username: string;
  apiKeyName: string;
  target: string | null;
  confirm: boolean;
  json: boolean;
}

export async function codexPoolUsers(
  config: UniDeskConfig,
  args: string[],
): Promise<Record<string, unknown> | RenderedCliResult> {
  const [action = "help", ...rest] = args;
  if (action === "help" || action === "--help" || action === "-h") return usersHelp();
  if (action !== "ensure") throw new Error("users usage: ensure --email <email> --username <name> --api-key-name <name> [--target <id>] [--confirm] [--json]");
  const options = parseEnsureOptions(rest);
  const target = codexPoolRuntimeTarget(options.target ?? undefined);
  if (target.runtimeMode !== "host-docker") {
    throw new Error(`users ensure currently requires a host-docker target; ${target.id} uses ${target.runtimeMode}`);
  }
  const credentialPath = credentialSourceRef(options.email);
  const plan = {
    ok: true,
    action: "platform-infra-sub2api-codex-pool-users-ensure",
    target: { id: target.id, runtimeMode: target.runtimeMode },
    user: { email: options.email, username: options.username, role: "user" },
    apiKey: { name: options.apiKeyName },
    credential: { sourceRef: credentialPath, present: false },
    mutation: options.confirm,
    valuesPrinted: false,
  };
  if (!options.confirm) return options.json ? plan : renderUsersEnsure(plan);

  const pool = readCodexPoolConfig();
  const result = await capture(config, target.route, ["sh"], ensureUserScript({
    runtimeMode: target.runtimeMode,
    namespace: target.namespace,
    appSecretName: target.appSecretName,
    hostDockerAppPort: target.hostDockerAppPort,
    hostDockerEnvPath: target.hostDockerEnvPath,
    adminEmailDefault: pool.adminEmailDefault,
    email: options.email,
    username: options.username,
    apiKeyName: options.apiKeyName,
  }));
  const parsed = parseJsonOutput(result.stdout);
  if (result.exitCode !== 0 || parsed?.ok !== true) {
    return {
      ok: false,
      action: "platform-infra-sub2api-codex-pool-users-ensure",
      target: { id: target.id, runtimeMode: target.runtimeMode },
      error: result.stderr.trim().slice(-2000) || "remote user fixture ensure failed",
      mutation: true,
      valuesPrinted: false,
    };
  }
  const credential = record(parsed.credential);
  const email = requiredString(credential.email, "credential.email");
  const password = requiredString(credential.password, "credential.password");
  mkdirSync(dirname(credentialPath), { recursive: true, mode: 0o700 });
  chmodSync(dirname(credentialPath), 0o700);
  writeFileSync(credentialPath, `${JSON.stringify({ email, password })}\n`, { encoding: "utf8", mode: 0o600 });
  chmodSync(credentialPath, 0o600);
  delete parsed.credential;
  const report = {
    ...parsed,
    credential: {
      sourceRef: credentialPath,
      present: true,
      fingerprint: `sha256:${createHash("sha256").update(`${email}\0${password}`).digest("hex")}`,
    },
    target: { id: target.id, runtimeMode: target.runtimeMode },
    mutation: true,
    valuesPrinted: false,
  };
  return options.json ? report : renderUsersEnsure(report);
}

function parseEnsureOptions(args: string[]): UserEnsureOptions {
  let email = "";
  let username = "";
  let apiKeyName = "";
  let target: string | null = null;
  let confirm = false;
  let json = false;
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index]!;
    if (arg === "--confirm") {
      confirm = true;
      continue;
    }
    if (arg === "--json") {
      json = true;
      continue;
    }
    const [name, inline] = arg.includes("=") ? arg.split(/=(.*)/su, 2) : [arg, null];
    if (!["--email", "--username", "--api-key-name", "--target"].includes(name)) throw new Error(`unsupported option: ${arg}`);
    const value = inline ?? args[++index];
    if (!value || value.startsWith("--")) throw new Error(`${name} requires a value`);
    if (name === "--email") email = value.trim().toLowerCase();
    if (name === "--username") username = value.trim();
    if (name === "--api-key-name") apiKeyName = value.trim();
    if (name === "--target") target = value.trim();
  }
  if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/u.test(email)) throw new Error("users ensure requires a valid --email");
  if (!username) throw new Error("users ensure requires --username");
  if (!apiKeyName) throw new Error("users ensure requires --api-key-name");
  return { email, username, apiKeyName, target, confirm, json };
}

function credentialSourceRef(email: string): string {
  const id = createHash("sha256").update(email).digest("hex").slice(0, 16);
  return rootPath(".state", "secrets", "sub2api-test-users", `${id}.json`);
}

function ensureUserScript(input: {
  runtimeMode: string;
  namespace: string;
  appSecretName: string;
  hostDockerAppPort: number;
  hostDockerEnvPath: string;
  adminEmailDefault: string;
  email: string;
  username: string;
  apiKeyName: string;
}): string {
  const encoded = Buffer.from(JSON.stringify(input), "utf8").toString("base64");
  return `set -eu
python3 - <<'PY'
import base64
import json
import secrets
import subprocess
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError

cfg = json.loads(base64.b64decode(${JSON.stringify(encoded)}).decode("utf-8"))

def run(command):
    return subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

def host_env():
    if cfg["runtimeMode"] != "host-docker":
        return {}
    try:
        lines = open(cfg["hostDockerEnvPath"], "r", encoding="utf-8").read().splitlines()
    except PermissionError:
        proc = run(["sudo", "-n", "cat", cfg["hostDockerEnvPath"]])
        if proc.returncode != 0:
            raise RuntimeError("cannot read host runtime environment")
        lines = proc.stdout.decode("utf-8", errors="replace").splitlines()
    values = {}
    for line in lines:
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        value = value.strip()
        if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
            value = value[1:-1]
        values[key.strip()] = value
    return values

def runtime_value(key, secret=False):
    if cfg["runtimeMode"] == "host-docker":
        return host_env().get(key)
    kind = "secret" if secret else "configmap"
    name = cfg["appSecretName"] if secret else "sub2api-config"
    proc = run(["kubectl", "-n", cfg["namespace"], "get", kind, name, "-o", "json"])
    if proc.returncode != 0:
        return None
    value = (json.loads(proc.stdout.decode("utf-8")).get("data") or {}).get(key)
    return base64.b64decode(value).decode("utf-8") if secret and value else value

def api(method, path, token=None, payload=None):
    port = cfg["hostDockerAppPort"] if cfg["runtimeMode"] == "host-docker" else 8080
    url = "http://127.0.0.1:" + str(port) + path
    data = None if payload is None else json.dumps(payload, separators=(",", ":")).encode("utf-8")
    headers = {"Accept": "application/json"}
    if data is not None:
        headers["Content-Type"] = "application/json"
    if token:
        headers["Authorization"] = "Bearer " + token
    request = Request(url, data=data, headers=headers, method=method)
    try:
        response = urlopen(request, timeout=20)
        status = response.status
        body = response.read()
    except HTTPError as error:
        status = error.code
        body = error.read()
    parsed = json.loads(body.decode("utf-8")) if body else {}
    if status < 200 or status >= 300 or (isinstance(parsed, dict) and parsed.get("code") not in (None, 0)):
        message = parsed.get("message") if isinstance(parsed, dict) else "request failed"
        raise RuntimeError(path + " failed: http=" + str(status) + " message=" + str(message)[:300])
    return parsed.get("data") if isinstance(parsed, dict) and "data" in parsed else parsed

def token_of(value):
    if isinstance(value, dict):
        for key in ("access_token", "token"):
            if isinstance(value.get(key), str) and value[key]:
                return value[key]
        for child in value.values():
            found = token_of(child)
            if found:
                return found
    return None

admin_email = runtime_value("ADMIN_EMAIL") or cfg["adminEmailDefault"]
admin_password = runtime_value("ADMIN_PASSWORD", secret=True)
if not admin_password:
    raise RuntimeError("ADMIN_PASSWORD missing")
admin_token = token_of(api("POST", "/api/v1/auth/login", payload={"email": admin_email, "password": admin_password}))
if not admin_token:
    raise RuntimeError("admin login returned no token")

query = urlencode({"page": 1, "page_size": 100, "search": cfg["email"]})
listed = api("GET", "/api/v1/admin/users?" + query, token=admin_token)
items = listed.get("items") if isinstance(listed, dict) else listed
matches = [item for item in (items or []) if str(item.get("email") or "").lower() == cfg["email"].lower()]
if len(matches) > 1:
    raise RuntimeError("email matched multiple users")
password = secrets.token_urlsafe(24)
payload = {
    "email": cfg["email"],
    "password": password,
    "username": cfg["username"],
    "role": "user",
    "notes": "UniDesk managed L1 test user",
    "concurrency": 1,
}
if matches:
    user = matches[0]
    if user.get("role") == "admin":
        raise RuntimeError("refusing to reuse an administrator as a test user")
    user = api("PUT", "/api/v1/admin/users/" + str(user["id"]), token=admin_token, payload={
        "password": password,
        "username": cfg["username"],
        "role": "user",
        "status": "active",
        "notes": "UniDesk managed L1 test user",
        "concurrency": 1,
    })
    user_action = "updated"
else:
    user = api("POST", "/api/v1/admin/users", token=admin_token, payload=payload)
    user_action = "created"

user_token = token_of(api("POST", "/api/v1/auth/login", payload={"email": cfg["email"], "password": password}))
if not user_token:
    raise RuntimeError("test user login returned no token")
keys = api("GET", "/api/v1/keys?page=1&page_size=100", token=user_token)
key_items = keys.get("items") if isinstance(keys, dict) else keys
key = next((item for item in (key_items or []) if item.get("name") == cfg["apiKeyName"]), None)
if key is None:
    key = api("POST", "/api/v1/keys", token=user_token, payload={"name": cfg["apiKeyName"]})
    key_action = "created"
else:
    key_action = "kept"

print(json.dumps({
    "ok": True,
    "user": {
        "id": user.get("id"),
        "email": user.get("email"),
        "username": user.get("username"),
        "role": user.get("role"),
        "status": user.get("status"),
        "action": user_action,
    },
    "apiKey": {
        "id": key.get("id"),
        "name": key.get("name"),
        "status": key.get("status"),
        "action": key_action,
    },
    "credential": {"email": cfg["email"], "password": password},
    "valuesPrinted": False,
}, ensure_ascii=False))
PY`;
}

function renderUsersEnsure(value: Record<string, unknown>): RenderedCliResult {
  const user = record(value.user);
  const apiKey = record(value.apiKey);
  const credential = record(value.credential);
  const lines = [
    "Sub2API user ensure",
    renderTable([
      ["USER", textValue(user.email)],
      ["USER_ID", textValue(user.id)],
      ["ROLE", textValue(user.role)],
      ["USER_ACTION", textValue(user.action)],
      ["API_KEY", textValue(apiKey.name)],
      ["API_KEY_ID", textValue(apiKey.id)],
      ["KEY_ACTION", textValue(apiKey.action)],
      ["CREDENTIAL_SOURCE", textValue(credential.sourceRef)],
      ["CREDENTIAL_PRESENT", textValue(credential.present)],
      ["MUTATION", textValue(value.mutation)],
      ["VALUES_PRINTED", "false"],
    ]),
  ];
  if (value.mutation !== true) lines.push("NEXT bun scripts/cli.ts platform-infra sub2api codex-pool users ensure ... --confirm");
  return renderedCliResult(value.ok === true, "platform-infra sub2api codex-pool users ensure", lines.join("\n"));
}

function usersHelp(): Record<string, unknown> {
  return {
    ok: true,
    command: "platform-infra sub2api codex-pool users ensure",
    usage: "bun scripts/cli.ts platform-infra sub2api codex-pool users ensure --email <email> --username <name> --api-key-name <name> [--target PK01] [--confirm] [--json]",
    defaultMutation: false,
    valuesPrinted: false,
  };
}

function record(value: unknown): Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}

function requiredString(value: unknown, name: string): string {
  if (typeof value !== "string" || value === "") throw new Error(`${name} is missing`);
  return value;
}
