/**
 * Session handling. The subscriberId (tel:8801...) is the proof of session.
 * We store it in an httpOnly cookie signed with an HMAC so it can't be forged
 * client-side. No passwords, no external auth — the bdapps subscription is the
 * source of truth.
 *
 * Server-only.
 */
import { cookies } from 'next/headers';
import crypto from 'node:crypto';
import { env } from './env';
import { isRegistered } from './repo';

const COOKIE_NAME = 'wk_session';
const PARENT_COOKIE = 'wk_parent';
const MAX_AGE_SECONDS = 24 * 60 * 60; // 24 hours
const PARENT_MAX_AGE = 30 * 60; // 30 minutes of parent access per unlock

function sign(value: string): string {
  const mac = crypto.createHmac('sha256', env.sessionSecret).update(value).digest('base64url');
  return `${value}.${mac}`;
}

function unsign(signed: string): string | null {
  const idx = signed.lastIndexOf('.');
  if (idx === -1) return null;
  const value = signed.slice(0, idx);
  const mac = signed.slice(idx + 1);
  const expected = crypto
    .createHmac('sha256', env.sessionSecret)
    .update(value)
    .digest('base64url');
  // constant-time compare
  const a = Buffer.from(mac);
  const b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return null;
  return value;
}

/** Set the session cookie for a subscriber. */
export function setSessionCookie(subscriberId: string) {
  cookies().set(COOKIE_NAME, sign(subscriberId), {
    httpOnly: true,
    sameSite: 'lax',
    secure: process.env.NODE_ENV === 'production',
    path: '/',
    maxAge: MAX_AGE_SECONDS,
  });
}

export function clearSessionCookie() {
  cookies().set(COOKIE_NAME, '', {
    httpOnly: true,
    sameSite: 'lax',
    secure: process.env.NODE_ENV === 'production',
    path: '/',
    maxAge: 0,
  });
}

/** Read subscriberId from a valid cookie, or null. */
export function getSessionSubscriberId(): string | null {
  const raw = cookies().get(COOKIE_NAME)?.value;
  if (!raw) return null;
  return unsign(raw);
}

/**
 * The subscriber is "active" when a valid cookie exists AND the local DB shows
 * REGISTERED. In demo mode we still require the cookie, but treat any present
 * subscriber as registered.
 */
export function getActiveSubscriberId(): string | null {
  const id = getSessionSubscriberId();
  if (!id) return null;
  if (env.demoMode) return id;
  return isRegistered(id) ? id : null;
}

/* --------------------------- parent gate --------------------------- */

/** Grant parent access (after a correct PIN) for the current subscriber. */
export function setParentCookie(subscriberId: string) {
  cookies().set(PARENT_COOKIE, sign(`parent:${subscriberId}`), {
    httpOnly: true,
    sameSite: 'lax',
    secure: process.env.NODE_ENV === 'production',
    path: '/',
    maxAge: PARENT_MAX_AGE,
  });
}

export function clearParentCookie() {
  cookies().set(PARENT_COOKIE, '', {
    httpOnly: true,
    sameSite: 'lax',
    secure: process.env.NODE_ENV === 'production',
    path: '/',
    maxAge: 0,
  });
}

/** True when the current subscriber has an unexpired parent unlock. */
export function isParentUnlocked(subscriberId: string): boolean {
  const raw = cookies().get(PARENT_COOKIE)?.value;
  if (!raw) return false;
  const value = unsign(raw);
  return value === `parent:${subscriberId}`;
}
