import { describe, expect, it } from "vitest";
import { db } from "@/server/db";
import { AUTH_CONFIG } from "@/server/auth/config";
import {
  createSession,
  revokeAllUserSessions,
  revokeSessionByToken,
  validateSessionToken,
} from "@/server/auth/session-service";
import { createTestUser } from "./fixtures";

const meta = { ip: "203.0.113.10", userAgent: "vitest" };
const HOUR = 60 * 60 * 1000;

describe("sesiones persistidas en PostgreSQL", () => {
  it("guarda solo el hash del token y valida devolviendo usuario y permisos del rol", async () => {
    const user = await createTestUser("ADVISOR");
    const { token, sessionId } = await createSession(user.id, meta);

    const row = await db.session.findUniqueOrThrow({ where: { id: sessionId } });
    expect(row.tokenHash).not.toBe(token);
    expect(row.tokenHash).not.toContain(token);

    const session = await validateSessionToken(token);
    expect(session?.user.id).toBe(user.id);
    expect(session?.user.roleKey).toBe("ADVISOR");
    expect(session?.permissions.has("leads.read.own")).toBe(true);
    expect(session?.permissions.has("users.read")).toBe(false);
  });

  it("expira por inactividad (8 h)", async () => {
    const user = await createTestUser("ADVISOR");
    const start = new Date();
    const { token } = await createSession(user.id, meta, start);
    expect(await validateSessionToken(token, new Date(start.getTime() + 7 * HOUR))).not.toBeNull();
    // La validación anterior renovó la ventana de inactividad hasta start+15h.
    expect(await validateSessionToken(token, new Date(start.getTime() + 14 * HOUR))).not.toBeNull();
    expect(await validateSessionToken(token, new Date(start.getTime() + 23 * HOUR))).toBeNull();
  });

  it("expira de forma absoluta (7 días) aunque haya actividad", async () => {
    const user = await createTestUser("ADVISOR");
    const start = new Date();
    const { token } = await createSession(user.id, meta, start);
    let now = start.getTime();
    // Actividad cada 6 h durante 7 días.
    while (now + 6 * HOUR < start.getTime() + AUTH_CONFIG.absoluteTimeoutMs) {
      now += 6 * HOUR;
      expect(await validateSessionToken(token, new Date(now))).not.toBeNull();
    }
    expect(
      await validateSessionToken(
        token,
        new Date(start.getTime() + AUTH_CONFIG.absoluteTimeoutMs + 1),
      ),
    ).toBeNull();
  });

  it("logout: el token revocado deja de servir", async () => {
    const user = await createTestUser("ADMIN");
    const { token } = await createSession(user.id, meta);
    expect(await revokeSessionByToken(token, "logout")).toMatchObject({ userId: user.id });
    expect(await validateSessionToken(token)).toBeNull();
    // Revocar dos veces no hace nada.
    expect(await revokeSessionByToken(token, "logout")).toBeNull();
  });

  it("desactivar al usuario invalida sus sesiones aunque no estén revocadas", async () => {
    const user = await createTestUser("SUPERVISOR");
    const { token } = await createSession(user.id, meta);
    await db.user.update({ where: { id: user.id }, data: { isActive: false } });
    expect(await validateSessionToken(token)).toBeNull();
  });

  it("revokeAllUserSessions cierra todas las sesiones del usuario", async () => {
    const user = await createTestUser("ADVISOR");
    const a = await createSession(user.id, meta);
    const b = await createSession(user.id, meta);
    expect(await revokeAllUserSessions(user.id, "role_changed")).toBe(2);
    expect(await validateSessionToken(a.token)).toBeNull();
    expect(await validateSessionToken(b.token)).toBeNull();
  });

  it("los permisos se leen de la BD en cada validación (cambio de rol inmediato)", async () => {
    const user = await createTestUser("ADVISOR");
    const { token } = await createSession(user.id, meta);
    const admin = await db.role.findUniqueOrThrow({ where: { key: "ADMIN" } });
    await db.user.update({ where: { id: user.id }, data: { roleId: admin.id } });
    const session = await validateSessionToken(token);
    expect(session?.permissions.has("users.read")).toBe(true);
  });

  it("tokens inexistentes o mal formados → null", async () => {
    expect(await validateSessionToken("a".repeat(43))).toBeNull();
    expect(await validateSessionToken("' OR 1=1 --")).toBeNull();
  });
});
