import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { roleRouter } from "../router/role.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(roleRouter);

function createContext(
  db: Record<string, unknown>,
  options: {
    role?: SystemRole;
    session?: boolean;
  } = {},
) {
  const { role = SystemRole.USER, session = true } = options;

  return {
    session: session
      ? {
          user: { email: "user@example.com", name: "User", image: null },
          expires: "2099-01-01T00:00:00.000Z",
        }
      : null,
    db: db as never,
    dbUser: session
      ? {
          id: role === SystemRole.ADMIN ? "user_admin" : "user_1",
          systemRole: role,
          permissionOverrides: null,
        }
      : null,
  };
}

describe("role router authorization", () => {
  describe("unauthenticated access", () => {
    it("rejects unauthenticated list call with UNAUTHORIZED", async () => {
      const roleFindMany = vi.fn();
      const caller = createCaller(
        createContext({ role: { findMany: roleFindMany } }, { session: false }),
      );

      await expect(caller.list({})).rejects.toMatchObject({
        code: "UNAUTHORIZED",
        message: "Authentication required",
      });

      expect(roleFindMany).not.toHaveBeenCalled();
    });

    it("rejects unauthenticated create call with UNAUTHORIZED", async () => {
      const roleCreate = vi.fn();
      const caller = createCaller(
        createContext({ role: { create: roleCreate } }, { session: false }),
      );

      await expect(
        caller.create({ name: "Art Director" }),
      ).rejects.toMatchObject({
        code: "UNAUTHORIZED",
        message: "Authentication required",
      });

      expect(roleCreate).not.toHaveBeenCalled();
    });
  });

  describe("USER role — insufficient permissions for mutations", () => {
    it("forbids USER from calling create", async () => {
      const roleCreate = vi.fn();
      const caller = createCaller(
        createContext({ role: { create: roleCreate } }),
      );

      await expect(
        caller.create({ name: "Art Director" }),
      ).rejects.toMatchObject({
        code: "FORBIDDEN",
        message: "Manager or Admin role required",
      });

      expect(roleCreate).not.toHaveBeenCalled();
    });

    it("forbids USER from calling update", async () => {
      const roleUpdate = vi.fn();
      const caller = createCaller(
        createContext({ role: { update: roleUpdate } }),
      );

      await expect(
        caller.update({ id: "role_1", data: { name: "Updated Role" } }),
      ).rejects.toMatchObject({
        code: "FORBIDDEN",
        message: "Manager or Admin role required",
      });

      expect(roleUpdate).not.toHaveBeenCalled();
    });

    it("forbids USER from calling delete", async () => {
      const roleDelete = vi.fn();
      const caller = createCaller(
        createContext({ role: { delete: roleDelete } }),
      );

      await expect(caller.delete({ id: "role_1" })).rejects.toMatchObject({
        code: "FORBIDDEN",
        message: "Manager or Admin role required",
      });

      expect(roleDelete).not.toHaveBeenCalled();
    });

    it("forbids USER from calling deactivate", async () => {
      const roleUpdate = vi.fn();
      const caller = createCaller(
        createContext({ role: { update: roleUpdate } }),
      );

      await expect(caller.deactivate({ id: "role_1" })).rejects.toMatchObject({
        code: "FORBIDDEN",
        message: "Manager or Admin role required",
      });

      expect(roleUpdate).not.toHaveBeenCalled();
    });
  });

  describe("MANAGER role — permitted for mutations", () => {
    it("allows MANAGER to call create without auth error", async () => {
      const createdRole = {
        id: "role_new",
        name: "Art Director",
        description: null,
        color: null,
        isActive: true,
        _count: { resourceRoles: 0 },
      };
      const roleCreate = vi.fn().mockResolvedValue(createdRole);
      const roleFindUnique = vi.fn().mockResolvedValue(null); // name not taken
      const auditLogCreate = vi.fn().mockResolvedValue({});
      // planningEntry count queries (attachZeroAllocationCount path)
      const planningEntryFindMany = vi.fn().mockResolvedValue([]);

      const caller = createCaller(
        createContext(
          {
            role: { create: roleCreate, findUnique: roleFindUnique },
            auditLog: { create: auditLogCreate },
            planningEntry: { findMany: planningEntryFindMany },
          },
          { role: SystemRole.MANAGER },
        ),
      );

      // Should not throw UNAUTHORIZED or FORBIDDEN
      const result = await caller.create({ name: "Art Director" });

      expect(result).toMatchObject({ id: "role_new", name: "Art Director" });
      expect(roleCreate).toHaveBeenCalledTimes(1);
    });
  });

  describe("ADMIN role — permitted for mutations", () => {
    it("allows ADMIN to call delete without auth error", async () => {
      const existingRole = {
        id: "role_1",
        name: "Stale Role",
        description: null,
        color: null,
        isActive: true,
        _count: { resourceRoles: 0 },
      };
      const roleFindUnique = vi.fn().mockResolvedValue(existingRole);
      const roleDelete = vi.fn().mockResolvedValue(existingRole);
      const auditLogCreate = vi.fn().mockResolvedValue({});
      // attachSingleRolePlanningEntryCount calls countPlanningEntries
      // which needs both demandRequirement and assignment findMany
      const demandRequirementFindMany = vi.fn().mockResolvedValue([]);
      const assignmentFindMany = vi.fn().mockResolvedValue([]);

      const caller = createCaller(
        createContext(
          {
            role: {
              findUnique: roleFindUnique,
              delete: roleDelete,
            },
            auditLog: { create: auditLogCreate },
            demandRequirement: { findMany: demandRequirementFindMany },
            assignment: { findMany: assignmentFindMany },
          },
          { role: SystemRole.ADMIN },
        ),
      );

      const result = await caller.delete({ id: "role_1" });

      expect(result).toEqual({ success: true });
      expect(roleDelete).toHaveBeenCalledTimes(1);
    });
  });
});
