/**
 * Water Log — Admin PIN auth.
 *
 * Self-contained, PIN-only authentication for the `/water/admin/*`
 * portal. **Has no Neon Auth dependency** — no `getSession()`, no
 * `getAdminUser()`, no cookies read/written by anything other than
 * this module.
 *
 * Design:
 * - The `wl_admin_session` cookie is an opaque bearer token (a random
 *   128-bit ID). Cookie presence = authenticated. Cookie absence =
 *   redirect to /water/admin/login.
 * - No DB session table is written. The cookie expiry (`maxAge`) is
 *   the source of truth for session lifetime.
 * - The Tuxedo brand UUID is hardcoded here because this portal is
 *   Tuxedo-specific (see CLAUDE.md "Public Storefront Architecture"
 *   — the same hardcoded-brand pattern is used in
 *   `src/app/water/admin/page.tsx`).
 *
 * Used by:
 *   - `POST /api/water-admin-auth`        (login)
 *   - `src/app/water/admin/page.tsx`       (gate)
 *   - `src/app/admin/water-log/entries/...` (gate, "isWaterAdmin" branch)
 *   - `logoutWaterAdmin` in field.ts       (sign-out)
 */
import "server-only";
import { randomUUID } from "node:crypto";
import { eq } from "drizzle-orm";
import { withBrand } from "@/db/client";
import { waterAdminSettings } from "@/db/schema/water-log";
import { hashPin, verifyPin, validatePin } from "@/lib/water-log-pin";
import {
  WL_ADMIN_SESSION_COOKIE,
  type AdminSession,
  clearSessionCookie,
  readSessionCookie,
  setSessionCookie,
} from "@/lib/water-log/session-server";

// Re-export for back-compat — `TUXEDO_BRAND_ID` is now defined in
// `@/lib/water-log/brand.ts`. The water-log folder is the single
// source of truth for the Tuxedo-only hardcoding.
import { TUXEDO_BRAND_ID } from "@/lib/water-log/brand";
export { TUXEDO_BRAND_ID };

/** Session lifetime, in hours, read from `water_admin_settings.session_duration_hours`. */
const DEFAULT_SESSION_HOURS = 8;

/**
 * Verify the admin PIN against the DB-stored scrypt hash. On success,
 * sets the `wl_admin_session` cookie to a random opaque bearer token
 * with the configured session duration. Returns `{ ok: false, error }`
 * for any failure (validation, missing settings, hash mismatch).
 */
export async function verifyAdminPin(
  pin: string,
): Promise<{ ok: true; session: AdminSession } | { ok: false; error: string }> {
  const formatError = validatePin(pin);
  if (formatError) return { ok: false, error: formatError };

  const sessionId = randomUUID();
  let sessionDurationHours = DEFAULT_SESSION_HOURS;

  const result = await withBrand(TUXEDO_BRAND_ID, async (db) => {
    const rows = await db
      .select({
        enabled: waterAdminSettings.enabled,
        pinHash: waterAdminSettings.pinHash,
        sessionDurationHours: waterAdminSettings.sessionDurationHours,
      })
      .from(waterAdminSettings)
      .where(eq(waterAdminSettings.brandId, TUXEDO_BRAND_ID))
      .limit(1);
    const settings = rows[0];
    if (!settings) return { ok: false as const, error: "Admin portal not configured" };
    if (!settings.enabled) return { ok: false as const, error: "Admin portal is disabled" };
    if (!settings.pinHash) {
      return {
        ok: false as const,
        error: "No PIN configured — generate one in /admin/water-log/settings",
      };
    }
    if (!verifyPin(pin, settings.pinHash)) {
      // Constant-time-ish delay to make online brute-force slightly less attractive.
      await new Promise((r) => setTimeout(r, 200));
      return { ok: false as const, error: "Invalid PIN" };
    }
    sessionDurationHours = settings.sessionDurationHours ?? DEFAULT_SESSION_HOURS;
    return { ok: true as const };
  });

  if (!result.ok) return result;

  // Cookie set is OUTSIDE the DB transaction. (cookies().set() corrupts
  // Next.js's request-scoped AsyncLocalStorage if called from inside a
  // pg transaction — see commit ca79896 and `lib/water-log/session.ts`.)
  await setSessionCookie(WL_ADMIN_SESSION_COOKIE, sessionId, {
    hours: sessionDurationHours,
  });
  return { ok: true, session: { sessionId, brandId: TUXEDO_BRAND_ID } };
}

/**
 * Read the current admin session from the cookie. Returns `null` if
 * the cookie is missing or empty. Does NOT validate against a DB
 * session table — the cookie itself is the session.
 */
export async function getAdminSession(): Promise<AdminSession | null> {
  const sessionId = await readSessionCookie(WL_ADMIN_SESSION_COOKIE);
  if (!sessionId) return null;
  return { sessionId, brandId: TUXEDO_BRAND_ID };
}

/** Clear the admin session cookie. Idempotent. */
export async function clearAdminSession(): Promise<void> {
  await clearSessionCookie(WL_ADMIN_SESSION_COOKIE);
}

// Re-export `hashPin` so callers (e.g. a future "set PIN" UI) can
// produce hashes with the same scrypt params as `verifyPin` checks
// against.
export { hashPin };