/**
 * ─────────────────────────────────────────────────────────────────────────────
 * CLINERA SaaS AUTH — bulletproof session management
 * ─────────────────────────────────────────────────────────────────────────────
 *
 * Strategy:
 *  • DB-backed sessions (AppSession table) — fully revocable
 *  • Sessions are stored as cryptographically-random 64-byte hex tokens
 *  • httpOnly Secure SameSite=Lax cookie — never accessible from JS
 *  • Sessions slide (lastSeenAt updated on each request)
 *  • Hard expiry: 30 days; idle expiry: 7 days
 *  • Audit log on every auth event
 */

import { cookies, headers } from "next/headers";
import { randomBytes } from "crypto";
import bcrypt from "bcryptjs";
import { prisma } from "./db";

// ── Constants ─────────────────────────────────────────────────────────────────
const COOKIE_NAME    = "clinera_session";
const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;  // 30 days hard expiry
const IDLE_TTL_MS    =  7 * 24 * 60 * 60 * 1000;  // 7 days idle timeout
const BCRYPT_ROUNDS  = 12;

// ── Types ─────────────────────────────────────────────────────────────────────
export interface ClinicSession {
  id:         string;
  name:       string;
  slug:       string;
  email:      string;
  wahaStatus: string;
  timezone:   string;
  plan:       string;
  sessionId:  string;
}

// ── Password helpers ──────────────────────────────────────────────────────────
export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, BCRYPT_ROUNDS);
}

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

// ── Token generation ──────────────────────────────────────────────────────────
export function generateToken(): string {
  return randomBytes(64).toString("hex");
}

// ── Session creation ──────────────────────────────────────────────────────────
export async function createSession(
  clinicId: string,
  meta?: { ipAddress?: string; userAgent?: string }
): Promise<string> {
  const token     = generateToken();
  const expiresAt = new Date(Date.now() + SESSION_TTL_MS);

  await prisma.appSession.create({
    data: {
      clinicId,
      token,
      expiresAt,
      ipAddress: meta?.ipAddress,
      userAgent: meta?.userAgent,
    },
  });

  return token;
}

// ── Get session from cookie ───────────────────────────────────────────────────
export async function getSession(): Promise<ClinicSession | null> {
  const cookieStore = await cookies();
  const token = cookieStore.get(COOKIE_NAME)?.value;
  if (!token) return null;

  return resolveSession(token);
}

// ── Resolve a session token to clinic ────────────────────────────────────────
export async function resolveSession(token: string): Promise<ClinicSession | null> {
  const session = await prisma.appSession.findUnique({
    where: { token },
    include: {
      clinic: {
        select: {
          id: true, name: true, slug: true, email: true,
          wahaStatus: true, timezone: true, plan: true,
        },
      },
    },
  });

  if (!session) return null;

  const now = new Date();

  // Hard expiry check
  if (session.expiresAt < now) {
    await prisma.appSession.delete({ where: { token } }).catch(() => {});
    return null;
  }

  // Idle timeout check
  const idleSince = now.getTime() - session.lastSeenAt.getTime();
  if (idleSince > IDLE_TTL_MS) {
    await prisma.appSession.delete({ where: { token } }).catch(() => {});
    return null;
  }

  // Slide last-seen (fire-and-forget — don't block the request)
  prisma.appSession
    .update({ where: { token }, data: { lastSeenAt: now } })
    .catch(() => {});

  return {
    ...session.clinic,
    sessionId: session.id,
  };
}

// ── Set session cookie ────────────────────────────────────────────────────────
export async function setSessionCookie(token: string): Promise<void> {
  const cookieStore = await cookies();
  cookieStore.set({
    name:     COOKIE_NAME,
    value:    token,
    httpOnly: true,
    secure:   process.env.NODE_ENV === "production",
    sameSite: "lax",
    maxAge:   SESSION_TTL_MS / 1000,
    path:     "/",
  });
}

// ── Destroy session (logout) ──────────────────────────────────────────────────
export async function destroySession(): Promise<void> {
  const cookieStore = await cookies();
  const token = cookieStore.get(COOKIE_NAME)?.value;

  if (token) {
    await prisma.appSession.deleteMany({ where: { token } }).catch(() => {});
  }

  cookieStore.delete(COOKIE_NAME);
}

// ── Revoke all sessions for a clinic (password change, security event) ────────
export async function revokeAllSessions(clinicId: string): Promise<number> {
  const result = await prisma.appSession.deleteMany({ where: { clinicId } });
  return result.count;
}

// ── Audit log helper ──────────────────────────────────────────────────────────
export async function writeAuditLog(params: {
  clinicId:  string;
  action:    string;
  actor?:    string;
  resource?: string;
  metadata?: Record<string, unknown>;
  request?:  Request;
}): Promise<void> {
  let ipAddress: string | undefined;
  let userAgent: string | undefined;

  if (params.request) {
    const h = params.request.headers;
    ipAddress = h.get("x-forwarded-for")?.split(",")[0]?.trim()
             ?? h.get("x-real-ip")
             ?? undefined;
    userAgent = h.get("user-agent") ?? undefined;
  }

  await prisma.auditLog.create({
    data: {
      clinicId: params.clinicId,
      action:   params.action,
      actor:    params.actor,
      resource: params.resource,
      metadata: params.metadata ? JSON.stringify(params.metadata) : undefined,
      ipAddress,
      userAgent,
    },
  }).catch((err) => console.error("Audit log write failed:", err));
}

// ── Extract request meta ──────────────────────────────────────────────────────
export function extractRequestMeta(req: Request): { ipAddress?: string; userAgent?: string } {
  const h = req.headers;
  return {
    ipAddress: h.get("x-forwarded-for")?.split(",")[0]?.trim()
            ?? h.get("x-real-ip")
            ?? undefined,
    userAgent: h.get("user-agent") ?? undefined,
  };
}

// ── Purge expired sessions (called by cron) ───────────────────────────────────
export async function purgeExpiredSessions(): Promise<number> {
  const result = await prisma.appSession.deleteMany({
    where: { expiresAt: { lt: new Date() } },
  });
  return result.count;
}

// ── Legacy compatibility (used by old code during transition) ─────────────────
/** @deprecated use createSession + setSessionCookie instead */
export function createToken(clinicId: string): string {
  // Kept for backward compat — routes will be migrated to use createSession()
  const jwt = require("jsonwebtoken");
  const secret = process.env.JWT_SECRET || "clinera-dev-secret-change-in-production";
  return jwt.sign({ clinicId }, secret, { expiresIn: "30d" });
}

/** @deprecated use getSession() which now uses DB sessions */
export function verifyToken(token: string): { clinicId: string } | null {
  try {
    const jwt = require("jsonwebtoken");
    const secret = process.env.JWT_SECRET || "clinera-dev-secret-change-in-production";
    return jwt.verify(token, secret) as { clinicId: string };
  } catch {
    return null;
  }
}

/** @deprecated use setSessionCookie() */
export function getSessionCookieOptions(token: string) {
  return {
    name:     COOKIE_NAME,
    value:    token,
    httpOnly: true,
    secure:   process.env.NODE_ENV === "production",
    sameSite: "lax" as const,
    maxAge:   SESSION_TTL_MS / 1000,
    path:     "/",
  };
}
