import { promises as fs } from "fs";
import path from "path";
import { hashPassword, verifyPassword } from "./password";

/**
 * The user directory — who may sign in and which apps each may access. Admins
 * manage it from /admin. Stored as JSON at <cwd>/data/users.json (override with
 * USERS_PATH); under data/ so it survives the CI's rsync --delete and is never
 * committed (it holds password hashes).
 *
 * The bootstrap admin (HUB_ADMIN_EMAIL + HUB_ADMIN_PASSWORD) is env-only — an
 * escape hatch that always has admin + access to everything, so a fresh
 * environment is never locked out before any user exists.
 */
export interface DirUser {
  email: string; // normalised lowercase
  name: string;
  allowedApps: string[]; // app slugs; "*" = all apps
  admin: boolean;
  passwordHash?: string; // optional local password
}

export type Identity = { email: string; name: string; admin: boolean };

const FILE = process.env.USERS_PATH || path.join(process.cwd(), "data", "users.json");
const norm = (e: string) => e.trim().toLowerCase();
const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;

export async function readUsers(): Promise<DirUser[]> {
  try {
    const d = JSON.parse(await fs.readFile(FILE, "utf8"));
    return Array.isArray(d?.users) ? d.users : [];
  } catch {
    return [];
  }
}

async function writeUsers(users: DirUser[]): Promise<void> {
  await fs.mkdir(path.dirname(FILE), { recursive: true });
  await fs.writeFile(FILE, JSON.stringify({ users }, null, 2) + "\n", "utf8");
}

export async function findUser(email: string): Promise<DirUser | null> {
  const e = norm(email);
  return (await readUsers()).find((u) => u.email === e) || null;
}

function bootstrap(): { email: string; password: string } | null {
  const email = norm(process.env.HUB_ADMIN_EMAIL || "");
  const password = process.env.HUB_ADMIN_PASSWORD || "";
  return email && password ? { email, password } : null;
}

export function isBootstrapAdmin(email: string): boolean {
  const b = bootstrap();
  return !!b && norm(email) === b.email;
}

/** Verify a local username/password login → identity, or null. */
export async function verifyLogin(email: string, password: string): Promise<Identity | null> {
  const b = bootstrap();
  if (b && norm(email) === b.email && password.length > 0 && password === b.password) {
    return { email: b.email, name: "Administrator", admin: true };
  }
  const u = await findUser(email);
  if (u?.passwordHash && verifyPassword(password, u.passwordHash)) {
    return { email: u.email, name: u.name || u.email, admin: !!u.admin };
  }
  return null;
}

/** Resolve a (Google-verified) email to an identity, or null if not in the directory. */
export async function resolveEmail(email: string): Promise<Identity | null> {
  if (isBootstrapAdmin(email)) return { email: norm(email), name: "Administrator", admin: true };
  const u = await findUser(email);
  return u ? { email: u.email, name: u.name || u.email, admin: !!u.admin } : null;
}

/** Apps a user may access, resolved fresh. Admin/bootstrap (or "*") = all apps. */
export async function access(email: string): Promise<{ admin: boolean; all: boolean; apps: string[] }> {
  if (isBootstrapAdmin(email)) return { admin: true, all: true, apps: [] };
  const u = await findUser(email);
  if (!u) return { admin: false, all: false, apps: [] };
  return { admin: !!u.admin, all: !!u.admin || u.allowedApps.includes("*"), apps: u.allowedApps };
}

// ── Admin CRUD ──────────────────────────────────────────────────────────────

/** Add or update a directory user. Returns an error string, or the new list. */
export async function upsertUser(
  input: Partial<DirUser> & { email: string },
  newPassword?: string,
): Promise<{ error: string } | { users: DirUser[] }> {
  const email = norm(input.email);
  if (!EMAIL_RE.test(email)) return { error: "valid email required" };
  const users = await readUsers();
  const existing = users.find((u) => u.email === email);
  const merged: DirUser = {
    email,
    name: (input.name ?? existing?.name ?? "").trim() || email,
    allowedApps: Array.isArray(input.allowedApps) ? input.allowedApps : existing?.allowedApps ?? [],
    admin: input.admin ?? existing?.admin ?? false,
    passwordHash: existing?.passwordHash,
  };
  if (newPassword) merged.passwordHash = hashPassword(newPassword);
  const next = users.filter((u) => u.email !== email);
  next.push(merged);
  next.sort((a, b) => a.email.localeCompare(b.email));
  await writeUsers(next);
  return { users: next };
}

export async function removeUser(email: string): Promise<DirUser[]> {
  const e = norm(email);
  const next = (await readUsers()).filter((u) => u.email !== e);
  await writeUsers(next);
  return next;
}

/** Public-safe view (no password hashes) for the admin UI. */
export function publicUser(u: DirUser) {
  return { email: u.email, name: u.name, allowedApps: u.allowedApps, admin: u.admin, hasPassword: !!u.passwordHash };
}
