import bcrypt from "bcryptjs";
import { SignJWT, jwtVerify } from "jose";
import { cookies } from "next/headers";

const SESSION_COOKIE = "rakku_session";
const PENDING_2FA_COOKIE = "rakku_2fa_pending";
const PENDING_PWCHANGE_COOKIE = "rakku_pwchange_pending";
const secret = new TextEncoder().encode(process.env.SESSION_SECRET);

export async function hashPassword(password: string) {
  return bcrypt.hash(password, 12);
}

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

type SessionPayload = { clientId: string };

// crossOrigin=true für die Team-Desktop-App (lädt lokal per Electron
// loadFile, nicht von rakku.de) — SameSite=Lax wird bei Cross-Origin-fetch()
// vom Browser gar nicht erst mitgeschickt, dadurch schlug der Session-Check
// direkt nach dem Login lautlos fehl (Login "klappte" serverseitig, die App
// bekam die Cookie aber nie zu sehen). Der normale Web-Login bleibt bei Lax,
// da dort kein Cross-Origin-Bedarf besteht und Lax der bessere CSRF-Schutz ist.
export async function createSession(clientId: string, crossOrigin = false) {
  const token = await new SignJWT({ clientId } satisfies SessionPayload)
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime("30d")
    .sign(secret);

  const store = await cookies();
  store.set(SESSION_COOKIE, token, {
    httpOnly: true,
    secure: crossOrigin ? true : process.env.NODE_ENV === "production",
    sameSite: crossOrigin ? "none" : "lax",
    path: "/",
    maxAge: 60 * 60 * 24 * 30,
  });
}

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

export async function getSessionClientId(): Promise<string | null> {
  const store = await cookies();
  const token = store.get(SESSION_COOKIE)?.value;
  if (!token) return null;

  try {
    const { payload } = await jwtVerify(token, secret);
    return (payload as SessionPayload).clientId ?? null;
  } catch {
    return null;
  }
}

export function generateToken() {
  return crypto.randomUUID().replace(/-/g, "") + crypto.randomUUID().replace(/-/g, "");
}

// Kurzlebiger Zwischenschritt für den Login, wenn ein Mitarbeiter-Account
// 2FA aktiviert hat: Passwort ist bereits geprüft, aber die eigentliche
// Session (rakku_session) entsteht erst nach dem gültigen TOTP-/Recovery-Code.
// next/locale werden hier mitgespeichert, damit der zweite Schritt nach
// erfolgreicher Prüfung an dieselbe Stelle weiterleiten kann wie ein
// normaler Login.
type Pending2faPayload = { clientId: string; next: string; locale: string };

// crossOrigin=true für die Team-Desktop-App, siehe createSession() oben —
// derselbe Bug (SameSite=Lax-Cookie kommt beim nächsten Cross-Origin-fetch
// nicht mehr an) betraf auch diesen Zwischenschritt: Login "gelang"
// serverseitig bis zum 2FA-Schritt, die anschließende Code-Prüfung schlug
// dann mit "Anmeldung abgelaufen" fehl, weil die App die Pending-Cookie nie
// zu sehen bekam.
export async function createPending2faToken(clientId: string, next: string, locale: string, crossOrigin = false) {
  const token = await new SignJWT({ clientId, next, locale } satisfies Pending2faPayload)
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime("5m")
    .sign(secret);

  const store = await cookies();
  store.set(PENDING_2FA_COOKIE, token, {
    httpOnly: true,
    secure: crossOrigin ? true : process.env.NODE_ENV === "production",
    sameSite: crossOrigin ? "none" : "lax",
    path: "/",
    maxAge: 60 * 5,
  });
}

export async function getPending2faPayload(): Promise<Pending2faPayload | null> {
  const store = await cookies();
  const token = store.get(PENDING_2FA_COOKIE)?.value;
  if (!token) return null;
  try {
    const { payload } = await jwtVerify(token, secret);
    return payload as unknown as Pending2faPayload;
  } catch {
    return null;
  }
}

export async function destroyPending2faToken() {
  const store = await cookies();
  store.delete(PENDING_2FA_COOKIE);
}

// Gleiches Muster wie der 2FA-Zwischenschritt oben: Passwort ist bereits
// geprüft, aber statt sofort eine echte Session zu erzeugen, muss erst ein
// neues Passwort gesetzt werden (siehe Client.mustChangePassword — z. B. bei
// spontan vom Team angelegten Mitarbeiter-Konten mit einem Übergangspasswort).
type PendingPasswordChangePayload = { clientId: string; next: string; locale: string };

// crossOrigin=true für die Team-Desktop-App — siehe createPending2faToken().
export async function createPendingPasswordChangeToken(clientId: string, next: string, locale: string, crossOrigin = false) {
  const token = await new SignJWT({ clientId, next, locale } satisfies PendingPasswordChangePayload)
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime("15m")
    .sign(secret);

  const store = await cookies();
  store.set(PENDING_PWCHANGE_COOKIE, token, {
    httpOnly: true,
    secure: crossOrigin ? true : process.env.NODE_ENV === "production",
    sameSite: crossOrigin ? "none" : "lax",
    path: "/",
    maxAge: 60 * 15,
  });
}

export async function getPendingPasswordChangePayload(): Promise<PendingPasswordChangePayload | null> {
  const store = await cookies();
  const token = store.get(PENDING_PWCHANGE_COOKIE)?.value;
  if (!token) return null;
  try {
    const { payload } = await jwtVerify(token, secret);
    return payload as unknown as PendingPasswordChangePayload;
  } catch {
    return null;
  }
}

export async function destroyPendingPasswordChangeToken() {
  const store = await cookies();
  store.delete(PENDING_PWCHANGE_COOKIE);
}
