import crypto from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";

const ROOT = process.cwd();
const DATA_ROOT = process.env.CMS_DATA_DIR ? path.resolve(process.env.CMS_DATA_DIR) : path.join(ROOT, ".runtime");
const AUTH_DIR = path.join(DATA_ROOT, "auth");
const PASSWORD_FILE = path.join(AUTH_DIR, "password.json");
const ENV_PASSWORD = process.env.CMS_PASSWORD || "admin";
const HASH_LENGTH = 64;
const SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1, maxmem: 32 * 1024 * 1024 };

type PasswordState = {
  version: 1;
  salt: string;
  hash: string;
  sessionVersion: number;
  updatedAt: string;
};

const safeEqual = (leftValue: unknown, rightValue: unknown): boolean => {
  const left = Buffer.from(String(leftValue));
  const right = Buffer.from(String(rightValue));
  return left.length === right.length && crypto.timingSafeEqual(left, right);
};

async function readPasswordState(): Promise<PasswordState | null> {
  try {
    const parsed = JSON.parse(await fs.readFile(PASSWORD_FILE, "utf8")) as Partial<PasswordState>;
    if (parsed.version !== 1 || typeof parsed.salt !== "string" || typeof parsed.hash !== "string" || !Number.isInteger(parsed.sessionVersion)) {
      throw new Error("后台密码文件格式无效");
    }
    return parsed as PasswordState;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
    throw error;
  }
}

async function hashPassword(password: string, salt: Buffer): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    crypto.scrypt(password, salt, HASH_LENGTH, SCRYPT_OPTIONS, (error, derivedKey) => {
      if (error) reject(error);
      else resolve(derivedKey);
    });
  });
}

export async function verifyPassword(password: unknown): Promise<boolean> {
  // Check the .env credential first so it remains a recovery password even if
  // the optional persisted password file is missing or damaged.
  if (safeEqual(password || "", ENV_PASSWORD)) return true;
  const state = await readPasswordState();
  if (!state) return false;
  const hash = await hashPassword(String(password || ""), Buffer.from(state.salt, "base64"));
  return safeEqual(hash.toString("base64"), state.hash);
}

export async function currentSessionVersion(): Promise<number> {
  try {
    return (await readPasswordState())?.sessionVersion || 0;
  } catch (error) {
    // Keep .env login and recovery available if the optional file is corrupt.
    console.error("读取后台密码文件失败：", error);
    return 0;
  }
}

export async function savePassword(password: string): Promise<void> {
  const previous = await readPasswordState().catch(() => null);
  const salt = crypto.randomBytes(16);
  const hash = await hashPassword(password, salt);
  const state: PasswordState = {
    version: 1,
    salt: salt.toString("base64"),
    hash: hash.toString("base64"),
    sessionVersion: (previous?.sessionVersion || 0) + 1,
    updatedAt: new Date().toISOString(),
  };
  await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });
  const temporaryFile = `${PASSWORD_FILE}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
  try {
    await fs.writeFile(temporaryFile, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, flag: "wx" });
    await fs.rename(temporaryFile, PASSWORD_FILE);
  } catch (error) {
    await fs.rm(temporaryFile, { force: true }).catch(() => {});
    throw error;
  }
}
