import "server-only";

import { spawn } from "node:child_process";
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { promises as fs } from "node:fs";
import path from "node:path";

type DbAdminUserRow = {
  id: string;
  passwordSalt: string;
  passwordHash: string;
};

function getDbPath(): string {
  const fromEnv = (process.env.CMS_DB_PATH || "").trim();
  if (fromEnv) return fromEnv;
  return path.join(process.cwd(), "content", "cms.sqlite");
}

function sqlString(value: string): string {
  return `'${value.replace(/'/g, "''")}'`;
}

function normalizeJsonOutput(out: string): string {
  const s = String(out || "").trim();
  if (!s) return "[]";
  try {
    JSON.parse(s);
    return s;
  } catch {
    const idxArr = s.lastIndexOf("\n[");
    const idxObj = s.lastIndexOf("\n{");
    const idx = Math.max(idxArr, idxObj);
    if (idx !== -1) {
      const candidate = s.slice(idx + 1).trim();
      try {
        JSON.parse(candidate);
        return candidate;
      } catch {
        // ignore
      }
    }
    return s;
  }
}

async function runSql(dbPath: string, sql: string, opts?: { json?: boolean }): Promise<string> {
  await fs.mkdir(path.dirname(dbPath), { recursive: true });

  return new Promise<string>((resolve, reject) => {
    const args: string[] = [];
    if (opts?.json) args.push("-json");
    args.push("-cmd", ".timeout 15000", dbPath);
    const child = spawn("sqlite3", args, { stdio: ["pipe", "pipe", "pipe"] });

    let out = "";
    let err = "";
    child.stdout.on("data", (d) => (out += String(d)));
    child.stderr.on("data", (d) => (err += String(d)));
    child.on("error", reject);
    child.on("close", (code) => {
      if (code === 0) {
        return resolve(opts?.json ? normalizeJsonOutput(out) : out);
      }
      reject(new Error(err || `sqlite3 exited with code ${code}`));
    });

    child.stdin.write(sql);
    child.stdin.end();
  });
}

let schemaPromise: Promise<string> | null = null;

async function ensureSchema(): Promise<string> {
  if (schemaPromise) return schemaPromise;

  schemaPromise = (async () => {
    const dbPath = getDbPath();
    const sql = `
PRAGMA journal_mode = WAL;

CREATE TABLE IF NOT EXISTS admin_users (
  id TEXT PRIMARY KEY,
  password_salt TEXT NOT NULL,
  password_hash TEXT NOT NULL,
  created_at TEXT NOT NULL,
  updated_at TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS admin_users_updated_idx ON admin_users(updated_at);
`;
    await runSql(dbPath, sql);
    return dbPath;
  })();

  try {
    return await schemaPromise;
  } catch (e) {
    schemaPromise = null;
    throw e;
  }
}

export function normalizeAdminId(input: string): string {
  const id = String(input || "").trim();
  if (!/^[A-Za-z0-9._-]{3,64}$/.test(id)) {
    throw new Error("Invalid admin id. Use 3-64 chars: letters, numbers, dot, underscore, hyphen.");
  }
  return id;
}

export function assertValidAdminPassword(password: string): void {
  const p = String(password || "");
  if (p.length < 8) throw new Error("Password must be at least 8 characters.");
  if (p.length > 256) throw new Error("Password is too long.");
}

function hashPassword(password: string, saltHex: string): string {
  const salt = Buffer.from(saltHex, "hex");
  return scryptSync(String(password || ""), salt, 64).toString("hex");
}

export function verifyPassword(password: string, saltHex: string, expectedHashHex: string): boolean {
  const got = Buffer.from(hashPassword(password, saltHex), "hex");
  const exp = Buffer.from(String(expectedHashHex || ""), "hex");
  if (got.length !== exp.length) return false;
  return timingSafeEqual(got, exp);
}

export async function countAdminUsers(): Promise<number> {
  const dbPath = await ensureSchema();
  const out = await runSql(dbPath, "SELECT COUNT(*) AS c FROM admin_users;\n", { json: true });
  const rows = JSON.parse(out || "[]") as Array<{ c?: number }>;
  return Number(rows?.[0]?.c || 0);
}

export async function getAdminUserById(idInput: string): Promise<DbAdminUserRow | null> {
  const dbPath = await ensureSchema();
  const id = normalizeAdminId(idInput);
  const sql = `
SELECT
  id,
  password_salt AS passwordSalt,
  password_hash AS passwordHash
FROM admin_users
WHERE id = ${sqlString(id)}
LIMIT 1;
`;
  const out = await runSql(dbPath, sql, { json: true });
  const rows = JSON.parse(out || "[]") as Array<DbAdminUserRow>;
  if (!rows.length) return null;
  return {
    id: String(rows[0].id),
    passwordSalt: String(rows[0].passwordSalt),
    passwordHash: String(rows[0].passwordHash),
  };
}

export async function createInitialAdminUser(input: { id: string; password: string }): Promise<{ id: string }> {
  const dbPath = await ensureSchema();
  const existing = await countAdminUsers();
  if (existing > 0) {
    throw new Error("Admin user already exists.");
  }

  const id = normalizeAdminId(input.id);
  assertValidAdminPassword(input.password);
  const salt = randomBytes(16).toString("hex");
  const hash = hashPassword(input.password, salt);
  const now = new Date().toISOString();

  const sql = `
INSERT INTO admin_users (id, password_salt, password_hash, created_at, updated_at)
VALUES (
  ${sqlString(id)},
  ${sqlString(salt)},
  ${sqlString(hash)},
  ${sqlString(now)},
  ${sqlString(now)}
);
`;
  await runSql(dbPath, sql);
  return { id };
}

