import { cookies } from "next/headers";
import crypto from "crypto";
import bcrypt from "bcryptjs";
import { publicUser } from "./db";
import { openStore } from "@/lib/data/store";
import type { User, StoredUser } from "./types";

const COOKIE_NAME = "notr_session";
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30; // 30 days

// In production, set SESSION_SECRET in your environment. This dev fallback
// is fine for localhost only — never ship a hardcoded secret to production.
const SESSION_SECRET = process.env.SESSION_SECRET || "dev-only-secret-change-me";

function sign(value: string): string {
  const hmac = crypto.createHmac("sha256", SESSION_SECRET).update(value).digest("hex");
  return `${value}.${hmac}`;
}

function verify(signed: string): string | null {
  const idx = signed.lastIndexOf(".");
  if (idx === -1) return null;
  const value = signed.slice(0, idx);
  const hmac = signed.slice(idx + 1);
  const expected = crypto.createHmac("sha256", SESSION_SECRET).update(value).digest("hex");
  // constant-time compare
  const a = Buffer.from(hmac);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null;
  return value;
}

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 10);
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

export async function createSession(userId: string) {
  // How long people stay signed in, from admin settings.
  const data = await openStore();
  const site = await data.settings();
  const days = Number(site.sessionDays ?? 30);

  const jar = await cookies();
  jar.set(COOKIE_NAME, sign(userId), {
    httpOnly: true,
    sameSite: "lax",
    secure: process.env.NODE_ENV === "production",
    path: "/",
    maxAge: days * 86400,
  });
}

export async function destroySession() {
  const store = await cookies();
  store.delete(COOKIE_NAME);
}

export async function getSessionUserId(): Promise<string | null> {
  const store = await cookies();
  const raw = store.get(COOKIE_NAME)?.value;
  if (!raw) return null;
  return verify(raw);
}

export async function getSessionUser(): Promise<User | null> {
  const userId = await getSessionUserId();
  if (!userId) return null;
  // One row by primary key. This runs on nearly every request, so it must
  // never be a scan — which is exactly what it was.
  const store = await openStore();
  const stored = await store.users.get(userId);
  if (!stored) return null;
  // A suspension/ban takes effect immediately, not just on next login — the
  // moment an admin flips this, every subsequent authenticated request from
  // that account starts failing as if they were never logged in.
  if (stored.status === "suspended" || stored.status === "banned") return null;
  return publicUser(stored);
}

// Like getSessionUser, but only succeeds for an active admin account — every
// /api/admin/* route should gate on this rather than checking isAdmin itself,
// so the suspended/banned check and the "not an admin" check both live here.
export async function getSessionAdmin(): Promise<StoredUser | null> {
  const userId = await getSessionUserId();
  if (!userId) return null;
  const store = await openStore();
  const stored = await store.users.get(userId);
  if (!stored || !stored.isAdmin) return null;
  if (stored.status === "suspended" || stored.status === "banned") return null;
  return stored;
}
