import { randomUUID } from "node:crypto";
import { spawn } from "node:child_process";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { SubrouterCodexAuth, SubrouterHttpClient } from "@awp/provider-account-subrouter";

export type AccountLoginState =
  "starting" | "waiting-for-user" | "importing" | "complete" | "failed";

export interface AccountLoginStatus {
  readonly id: string;
  readonly provider: "codex";
  readonly state: AccountLoginState;
  readonly output: string;
  readonly account?: string;
  readonly error?: string;
}

interface MutableLoginStatus {
  id: string;
  provider: "codex";
  state: AccountLoginState;
  output: string;
  account?: string;
  error?: string;
}

const maxOutputBytes = 24_000;

function sanitizeOutput(value: string): string {
  const withoutJwt = value.replace(
    /\b[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\b/gu,
    "<redacted-token>",
  );
  const withoutApiKeys = withoutJwt.replace(/\bsk-[A-Za-z0-9_-]{12,}\b/gu, "<redacted-key>");
  return withoutApiKeys.length > maxOutputBytes
    ? withoutApiKeys.slice(withoutApiKeys.length - maxOutputBytes)
    : withoutApiKeys;
}

function codexAuth(value: unknown): SubrouterCodexAuth {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    throw new Error("Codex login did not write a valid auth object");
  }
  const record = value as Record<string, unknown>;
  const tokens = record.tokens;
  if (typeof tokens !== "object" || tokens === null || Array.isArray(tokens)) {
    throw new Error("Codex login auth is missing tokens");
  }
  const tokenRecord = tokens as Record<string, unknown>;
  const accessToken = tokenRecord.access_token;
  const refreshToken = tokenRecord.refresh_token;
  const idToken = tokenRecord.id_token;
  const accountId = tokenRecord.account_id;
  if (
    typeof accessToken !== "string" ||
    accessToken.length === 0 ||
    typeof refreshToken !== "string" ||
    refreshToken.length === 0 ||
    typeof idToken !== "string" ||
    idToken.length === 0 ||
    (accountId !== undefined && typeof accountId !== "string")
  ) {
    throw new Error("Codex login auth is incomplete");
  }
  const lastRefresh = record.last_refresh;
  const authMode = record.auth_mode;
  return Object.freeze({
    tokens: Object.freeze({
      access_token: accessToken,
      refresh_token: refreshToken,
      id_token: idToken,
      ...(accountId === undefined ? {} : { account_id: accountId }),
    }),
    ...(typeof lastRefresh === "string" ? { last_refresh: lastRefresh } : {}),
    ...(typeof authMode === "string" ? { auth_mode: authMode } : {}),
  });
}

function snapshot(status: MutableLoginStatus): AccountLoginStatus {
  return Object.freeze({
    id: status.id,
    provider: status.provider,
    state: status.state,
    output: status.output,
    ...(status.account === undefined ? {} : { account: status.account }),
    ...(status.error === undefined ? {} : { error: status.error }),
  });
}

export class AccountLoginManager {
  private readonly sessions = new Map<string, MutableLoginStatus>();

  constructor(
    private readonly subrouter: SubrouterHttpClient,
    private readonly codexCommand: string = "codex",
  ) {}

  async startCodex(): Promise<AccountLoginStatus> {
    const id = randomUUID();
    const status: MutableLoginStatus = {
      id,
      provider: "codex",
      state: "starting",
      output: "Starting isolated Codex device login…\n",
    };
    this.sessions.set(id, status);
    const home = await mkdtemp(join(tmpdir(), "awp-codex-login-"));
    const childEnv: NodeJS.ProcessEnv = {
      ...process.env,
      HOME: home,
      CODEX_HOME: home,
      XDG_CONFIG_HOME: join(home, ".config"),
      XDG_DATA_HOME: join(home, ".local", "share"),
      XDG_STATE_HOME: join(home, ".local", "state"),
    };
    for (const key of ["OPENAI_API_KEY", "CODEX_API_KEY", "ANTHROPIC_API_KEY", "CLAUDE_API_KEY"]) {
      delete childEnv[key];
    }
    const child = spawn(this.codexCommand, ["login", "--device-auth"], {
      env: childEnv,
      stdio: ["ignore", "pipe", "pipe"],
    });
    const append = (chunk: Buffer): void => {
      status.output = sanitizeOutput(status.output + chunk.toString("utf8"));
      if (status.state === "starting") status.state = "waiting-for-user";
    };
    child.stdout.on("data", append);
    child.stderr.on("data", append);
    child.once("error", (error) => {
      status.state = "failed";
      status.error = error.message;
      void rm(home, { recursive: true, force: true });
    });
    child.once("close", (code) => {
      void (async () => {
        try {
          if (code !== 0) throw new Error(`Codex login exited with status ${code ?? "unknown"}`);
          status.state = "importing";
          status.output = sanitizeOutput(
            status.output + "\nLogin complete. Importing into K3s Subrouter…\n",
          );
          const raw = JSON.parse(await readFile(join(home, "auth.json"), "utf8")) as unknown;
          const imported = await this.subrouter.importCodexAuth(codexAuth(raw));
          status.account = imported.account;
          status.state = "complete";
          status.output = sanitizeOutput(
            status.output + `Account ${imported.account} is ready in K3s Subrouter.\n`,
          );
        } catch (error) {
          status.state = "failed";
          status.error = error instanceof Error ? error.message : "Account login failed";
        } finally {
          await rm(home, { recursive: true, force: true });
        }
      })();
    });
    return snapshot(status);
  }

  get(id: string): AccountLoginStatus | undefined {
    const status = this.sessions.get(id);
    return status ? snapshot(status) : undefined;
  }
}
