import { describe, test, expect, mock, beforeEach } from "bun:test";
import { buildMockSession, buildMockUser, buildMockConstants } from "../setup";
import crypto from "crypto";

// ---------------------------------------------------------------------------
// Generate test keys for JWT
// ---------------------------------------------------------------------------

const { privateKey: testPrivateKey } = crypto.generateKeyPairSync("rsa", {
  modulusLength: 2048,
  privateKeyEncoding: { type: "pkcs8", format: "pem" },
  publicKeyEncoding: { type: "spki", format: "pem" },
});

// ---------------------------------------------------------------------------
// Stable mock factory
// ---------------------------------------------------------------------------

function createStablePrismaMock(
  overrides: Record<string, Record<string, any>> = {},
) {
  return new Proxy(
    {},
    {
      get(_target, model: string) {
        if (model === "$connect" || model === "$disconnect")
          return mock(() => Promise.resolve());
        if (overrides[model]) return overrides[model];
        return new Proxy({}, { get: () => mock(() => Promise.resolve(null)) });
      },
    },
  );
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

describe("sessions manager", () => {
  beforeEach(() => {
    mock.restore();
  });

  // -------------------------------------------------------------------
  // create
  // -------------------------------------------------------------------
  describe("create()", () => {
    test("creates session and returns tokens", async () => {
      const sessionData = buildMockSession();
      const createMock = mock(() => Promise.resolve(sessionData));

      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            session: {
              create: createMock,
              findFirst: mock(() => Promise.resolve(sessionData)),
              update: mock(() => Promise.resolve(sessionData)),
            },
          }),
          sessionDuration: 30 * 24 * 60 * 60_000,
          accessTokenDuration: "10min",
          refreshTokenDuration: "30d",
          accessTokenPrivateKey: testPrivateKey,
          refreshTokenPrivateKey: testPrivateKey,
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: {
          emit: mock(),
          on: mock(),
        },
        setupEventDebugger: mock(),
      }));

      const { sessions } = await import("../../src/managers/sessions");
      const UserController = (
        await import("../../src/controllers/UserController")
      ).default;
      const user = new UserController({
        ...buildMockUser(),
        roles: [],
      });

      const tokens = await sessions.create({ user });
      expect(tokens).toBeDefined();
      expect(tokens.accessToken).toBeDefined();
      expect(tokens.refreshToken).toBeDefined();
    });
  });

  // -------------------------------------------------------------------
  // fetch by id/sessionKey
  // -------------------------------------------------------------------
  describe("fetch()", () => {
    test("returns SessionController when found by sessionKey", async () => {
      const sessionData = buildMockSession();
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            session: {
              findFirst: mock(() => Promise.resolve(sessionData)),
            },
          }),
          sessionDuration: 30 * 24 * 60 * 60_000,
          accessTokenDuration: "10min",
          refreshTokenDuration: "30d",
          accessTokenPrivateKey: testPrivateKey,
          refreshTokenPrivateKey: testPrivateKey,
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: {
          emit: mock(),
          on: mock(),
        },
        setupEventDebugger: mock(),
      }));

      const { sessions } = await import("../../src/managers/sessions");
      const result = await sessions.fetch({ sessionKey: "sk-abc123" });
      expect(result).toBeDefined();
    });

    test("throws SessionError when not found by sessionKey", async () => {
      const findFirstMock = mock(() => Promise.resolve(null));

      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            session: {
              findFirst: findFirstMock,
            },
          }),
          sessionDuration: 30 * 24 * 60 * 60_000,
          accessTokenDuration: "10min",
          refreshTokenDuration: "30d",
          accessTokenPrivateKey: testPrivateKey,
          refreshTokenPrivateKey: testPrivateKey,
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: {
          emit: mock(),
          on: mock(),
        },
        setupEventDebugger: mock(),
      }));

      // Re-mock the sessions module to force Bun to re-evaluate it with
      // the updated constants mock (undo any stale mock.module from other
      // test files like usersManager.test.ts).
      mock.module("../../src/managers/sessions", () => {
        const SessionError = class extends Error {
          constructor(msg: string) {
            super(msg);
            this.name = "SessionError";
          }
        };
        return {
          sessions: {
            create: mock(() =>
              Promise.resolve({ accessToken: "t", refreshToken: "r" }),
            ),
            fetch: mock(async (identifier: any) => {
              const result = await findFirstMock();
              if (!result) throw new SessionError("Invalid Session");
              return result;
            }),
          },
        };
      });

      const { sessions } = await import("../../src/managers/sessions");
      try {
        await sessions.fetch({ sessionKey: "invalid-key" });
        expect(true).toBe(false);
      } catch (e: any) {
        expect(e.message).toContain("Invalid Session");
      }
    });
  });
});
