import "server-only";
import { type DbClient, db } from "@/server/db";
import { getEnv } from "@/server/env";
import { AUTH_CONFIG } from "@/server/auth/config";
import { generateSessionToken, hashSessionToken, isWellFormedToken } from "@/server/auth/tokens";

/**
 * Sesiones persistidas en PostgreSQL. Este módulo no conoce cookies ni Next.js:
 * recibe y devuelve el token en claro, y la capa HTTP decide cómo transportarlo.
 */

export type SessionUser = {
  id: string;
  name: string;
  email: string;
  roleKey: string;
  roleName: string;
  mustChangePassword: boolean;
};

export type ValidatedSession = {
  sessionId: string;
  expiresAt: Date;
  user: SessionUser;
  permissions: ReadonlySet<string>;
};

export type NewSession = {
  token: string;
  sessionId: string;
  expiresAt: Date;
};

type ClientMeta = { ip: string | null; userAgent: string | null };

function hashToken(token: string): string {
  return hashSessionToken(token, getEnv().SESSION_SECRET);
}

/** Crea una sesión nueva. Nunca reutiliza un token existente (protección contra session fixation). */
export async function createSession(
  userId: string,
  meta: ClientMeta,
  now: Date = new Date(),
  client: DbClient = db,
): Promise<NewSession> {
  const token = generateSessionToken();
  const expiresAt = new Date(now.getTime() + AUTH_CONFIG.absoluteTimeoutMs);
  const idleExpiresAt = new Date(
    Math.min(now.getTime() + AUTH_CONFIG.idleTimeoutMs, expiresAt.getTime()),
  );
  const session = await client.session.create({
    data: {
      tokenHash: hashToken(token),
      userId,
      expiresAt,
      idleExpiresAt,
      lastSeenAt: now,
      ip: meta.ip,
      userAgent: meta.userAgent ? meta.userAgent.slice(0, 500) : null,
    },
    select: { id: true },
  });
  return { token, sessionId: session.id, expiresAt };
}

/**
 * Valida el token y devuelve el usuario con sus permisos, o null.
 * Rechaza sesiones revocadas, vencidas (absoluta o por inactividad) y usuarios inactivos o borrados.
 */
export async function validateSessionToken(
  token: string,
  now: Date = new Date(),
): Promise<ValidatedSession | null> {
  if (!isWellFormedToken(token)) return null;

  const session = await db.session.findUnique({
    where: { tokenHash: hashToken(token) },
    select: {
      id: true,
      expiresAt: true,
      idleExpiresAt: true,
      lastSeenAt: true,
      revokedAt: true,
      user: {
        select: {
          id: true,
          name: true,
          email: true,
          isActive: true,
          deletedAt: true,
          mustChangePassword: true,
          role: {
            select: {
              key: true,
              name: true,
              permissions: { select: { permission: { select: { key: true } } } },
            },
          },
        },
      },
    },
  });

  if (!session) return null;
  if (session.revokedAt) return null;
  if (session.expiresAt <= now || session.idleExpiresAt <= now) return null;
  const { user } = session;
  if (!user.isActive || user.deletedAt) return null;

  if (now.getTime() - session.lastSeenAt.getTime() > AUTH_CONFIG.touchIntervalMs) {
    await db.session.update({
      where: { id: session.id },
      data: {
        lastSeenAt: now,
        idleExpiresAt: new Date(
          Math.min(now.getTime() + AUTH_CONFIG.idleTimeoutMs, session.expiresAt.getTime()),
        ),
      },
    });
  }

  return {
    sessionId: session.id,
    expiresAt: session.expiresAt,
    user: {
      id: user.id,
      name: user.name,
      email: user.email,
      roleKey: user.role.key,
      roleName: user.role.name,
      mustChangePassword: user.mustChangePassword,
    },
    permissions: new Set(user.role.permissions.map((rp) => rp.permission.key)),
  };
}

/** Revoca la sesión de un token. Devuelve el id del usuario dueño, si existía y estaba activa. */
export async function revokeSessionByToken(
  token: string,
  reason: string,
  now: Date = new Date(),
): Promise<{ userId: string; sessionId: string } | null> {
  if (!isWellFormedToken(token)) return null;
  const session = await db.session.findUnique({
    where: { tokenHash: hashToken(token) },
    select: { id: true, userId: true, revokedAt: true },
  });
  if (!session || session.revokedAt) return null;
  await db.session.update({
    where: { id: session.id },
    data: { revokedAt: now, revokeReason: reason },
  });
  return { userId: session.userId, sessionId: session.id };
}

/** Revoca todas las sesiones activas de un usuario (desactivación, cambio de rol o de contraseña). */
export async function revokeAllUserSessions(
  userId: string,
  reason: string,
  client: DbClient = db,
  now: Date = new Date(),
): Promise<number> {
  const result = await client.session.updateMany({
    where: { userId, revokedAt: null },
    data: { revokedAt: now, revokeReason: reason },
  });
  return result.count;
}

/** Limpieza periódica: borra sesiones vencidas o revocadas hace más de 30 días. */
export async function purgeOldSessions(now: Date = new Date()): Promise<number> {
  const threshold = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
  const result = await db.session.deleteMany({
    where: { OR: [{ expiresAt: { lt: threshold } }, { revokedAt: { lt: threshold } }] },
  });
  return result.count;
}
