import { beforeEach, describe, expect, it, vi } from "vitest";

const { mockOptima, mockCheckPermissions, mockHandleApiError, mockFail } =
  vi.hoisted(() => ({
    mockOptima: {
      users: {
        fetchAll: vi.fn(),
        fetchRoles: vi.fn(),
        update: vi.fn(),
        delete: vi.fn(),
      },
      role: { fetchMany: vi.fn() },
      permission: { fetchCategorized: vi.fn() },
    },
    mockCheckPermissions: vi.fn(),
    mockHandleApiError: vi.fn(),
    mockFail: vi.fn((status: number, data: any) => ({
      status,
      data,
    })),
  }));

vi.mock("$env/static/public", () => ({
  PUBLIC_API_URL: "https://api.example.com",
}));

vi.mock("$lib", () => ({ optima: mockOptima }));
vi.mock("$lib/permissions", () => ({
  checkPermissions: mockCheckPermissions,
}));
vi.mock("$lib/optima-api/errorHandler", () => ({
  handleApiError: mockHandleApiError,
}));
vi.mock("@sveltejs/kit", () => ({
  fail: mockFail,
}));

import { load, actions } from "./+page.server";

describe("admin/users +page.server.ts", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe("load", () => {
    it("returns empty data when no token", async () => {
      const result = await load({ locals: {} } as any);
      expect(result).toEqual({
        users: [],
        roles: [],
        permissions: {},
      });
    });

    it("loads users with roles", async () => {
      mockOptima.users.fetchAll.mockResolvedValueOnce({
        data: [{ id: "u1", name: "John" }],
      });
      mockOptima.role.fetchMany.mockResolvedValueOnce({
        data: [{ id: "r1" }],
      });
      mockCheckPermissions.mockResolvedValueOnce({
        "admin.users.view": true,
      });
      mockOptima.permission.fetchCategorized.mockResolvedValueOnce({
        data: {},
      });
      mockOptima.users.fetchRoles.mockResolvedValueOnce({
        data: [{ id: "r1" }],
      });

      const result = await load({
        locals: { session: { accessToken: "tok" } },
      } as any);

      expect(result).toMatchObject({
        users: [
          expect.objectContaining({
            id: "u1",
            roleDetails: [{ id: "r1" }],
          }),
        ],
        roles: [{ id: "r1" }],
      });
    });
  });

  describe("actions", () => {
    function createFormData(entries: Record<string, string>) {
      return {
        get: (key: string) => entries[key] ?? null,
        getAll: (key: string) => (entries[key] ? [entries[key]] : []),
        has: (key: string) => key in entries,
      };
    }

    describe("updateUser", () => {
      it("returns 401 when no token", async () => {
        await actions.updateUser({
          locals: {},
          request: {
            formData: vi.fn().mockResolvedValue(createFormData({})),
          },
        } as any);
        expect(mockFail).toHaveBeenCalledWith(401, {
          message: "Not authenticated.",
        });
      });

      it("returns 400 when user ID is missing", async () => {
        await actions.updateUser({
          locals: { session: { accessToken: "tok" } },
          request: {
            formData: vi.fn().mockResolvedValue(createFormData({})),
          },
        } as any);
        expect(mockFail).toHaveBeenCalledWith(400, {
          message: "User ID is required.",
        });
      });

      it("updates user successfully", async () => {
        mockOptima.users.update.mockResolvedValueOnce({});

        const result = await actions.updateUser({
          locals: { session: { accessToken: "tok" } },
          request: {
            formData: vi.fn().mockResolvedValue(
              createFormData({
                id: "u1",
                firstName: "Updated",
                lastName: "User",
              })
            ),
          },
        } as any);

        expect(mockOptima.users.update).toHaveBeenCalledWith("tok", "u1", {
          firstName: "Updated",
          lastName: "User",
          image: undefined,
        });
        expect(result).toEqual({});
      });

      it("parses roles JSON when provided", async () => {
        mockOptima.users.update.mockResolvedValueOnce({});

        await actions.updateUser({
          locals: { session: { accessToken: "tok" } },
          request: {
            formData: vi.fn().mockResolvedValue(
              createFormData({
                id: "u1",
                firstName: "Updated",
                lastName: "User",
                roles: '["r1","r2"]',
              })
            ),
          },
        } as any);

        expect(mockOptima.users.update).toHaveBeenCalledWith("tok", "u1", {
          firstName: "Updated",
          lastName: "User",
          image: undefined,
          roles: ["r1", "r2"],
        });
      });

      it("returns 400 for invalid roles JSON", async () => {
        await actions.updateUser({
          locals: { session: { accessToken: "tok" } },
          request: {
            formData: vi.fn().mockResolvedValue(
              createFormData({
                id: "u1",
                firstName: "Updated",
                roles: "bad json",
              })
            ),
          },
        } as any);

        expect(mockFail).toHaveBeenCalledWith(400, {
          message: "Invalid roles data.",
        });
      });
    });

    describe("deleteUser", () => {
      it("returns 400 when id is missing", async () => {
        await actions.deleteUser({
          locals: { session: { accessToken: "tok" } },
          request: {
            formData: vi.fn().mockResolvedValue(createFormData({})),
          },
        } as any);
        expect(mockFail).toHaveBeenCalledWith(400, {
          message: "User ID is required.",
        });
      });

      it("deletes user successfully", async () => {
        mockOptima.users.delete.mockResolvedValueOnce({});

        const result = await actions.deleteUser({
          locals: { session: { accessToken: "tok" } },
          request: {
            formData: vi.fn().mockResolvedValue(createFormData({ id: "u1" })),
          },
        } as any);

        expect(mockOptima.users.delete).toHaveBeenCalledWith("tok", "u1");
        expect(result).toEqual({});
      });
    });
  });
});
