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

// ---------------------------------------------------------------------------
// 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("users manager", () => {
  beforeEach(() => {
    mock.restore();
  });

  // -------------------------------------------------------------------
  // userExists
  // -------------------------------------------------------------------
  describe("userExists()", () => {
    test("returns true when user exists", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              findFirst: mock(() => Promise.resolve(buildMockUser())),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.userExists({ email: "test@example.com" });
      expect(result).toBe(true);
    });

    test("returns false when user does not exist", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.userExists({ email: "nobody@example.com" });
      expect(result).toBe(false);
    });
  });

  // -------------------------------------------------------------------
  // fetchUser
  // -------------------------------------------------------------------
  describe("fetchUser()", () => {
    test("returns UserController when found", async () => {
      const userData = { ...buildMockUser(), roles: [] };
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              findFirst: mock(() => Promise.resolve(userData)),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.fetchUser({ email: "test@example.com" });
      expect(result).toBeDefined();
      expect(result!.id).toBe("user-1");
    });

    test("returns null when not found", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.fetchUser({ email: "nobody@test.com" });
      expect(result).toBeNull();
    });

    test("returns null when identifier is empty", async () => {
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock(),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.fetchUser({});
      expect(result).toBeNull();
    });
  });

  // -------------------------------------------------------------------
  // fetchAllUsers
  // -------------------------------------------------------------------
  describe("fetchAllUsers()", () => {
    test("returns array of UserControllers", async () => {
      const allUsers = [
        { ...buildMockUser(), roles: [] },
        {
          ...buildMockUser({ id: "user-2", email: "user2@test.com" }),
          roles: [],
        },
      ];
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              findMany: mock(() => Promise.resolve(allUsers)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.fetchAllUsers();
      expect(result).toBeArrayOfSize(2);
    });
  });

  // -------------------------------------------------------------------
  // deleteUser
  // -------------------------------------------------------------------
  describe("deleteUser()", () => {
    test("deletes user by id", async () => {
      const deleteMock = mock(() => Promise.resolve({}));
      const emitMock = mock();
      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              delete: deleteMock,
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: emitMock, on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve({})),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve(null)),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      await users.deleteUser("user-1");
      expect(deleteMock).toHaveBeenCalledWith({ where: { id: "user-1" } });
      expect(emitMock).toHaveBeenCalledWith("user:deleted", { id: "user-1" });
    });
  });

  // -------------------------------------------------------------------
  // createUser
  // -------------------------------------------------------------------
  describe("createUser()", () => {
    test("creates user from Microsoft token", async () => {
      const msData = {
        id: "ms-uid-new",
        mail: "newuser@test.com",
        userPrincipalName: "newuser@test.com",
        givenName: "New",
        surname: "User",
      };
      const newUserData = {
        ...buildMockUser({
          id: "user-new",
          userId: "ms-uid-new",
          email: "newuser@test.com",
        }),
        roles: [],
      };

      mock.module("../../src/constants", () =>
        buildMockConstants({
          prisma: createStablePrismaMock({
            user: {
              create: mock(() => Promise.resolve(newUserData)),
              findFirst: mock(() => Promise.resolve(null)),
            },
          }),
        }),
      );
      mock.module("../../src/modules/globalEvents", () => ({
        events: { emit: mock(), on: mock() },
        setupEventDebugger: mock(),
      }));
      mock.module("../../src/modules/fetchMicrosoftUser", () => ({
        fetchMicrosoftUser: mock(() => Promise.resolve(msData)),
      }));
      mock.module("../../src/modules/cw-utils/members/fetchAllMembers", () => ({
        findCwIdentifierByEmail: mock(() => Promise.resolve("newuser")),
      }));
      mock.module("../../src/managers/sessions", () => ({
        sessions: {
          create: mock(() =>
            Promise.resolve({
              accessToken: "mock-access",
              refreshToken: "mock-refresh",
            }),
          ),
          fetch: mock(() => Promise.resolve(null)),
        },
      }));

      const { users } = await import("../../src/managers/users");
      const result = await users.createUser("test-token");
      expect(result).toBeDefined();
      expect(result.id).toBe("user-new");
    });
  });
});
