import { describe, it, expect, vi, beforeEach } from "vitest";
import type { ExecutionContext } from "@nestjs/common";
import {
  PermissionGuard,
  Permissions,
  PERMISSIONS_KEY,
} from "./permission.guard.js";
import type { AuthenticatedRequest } from "./auth.middleware.js";
import { PermissionDeniedError } from "../shared/errors/application-error.js";

describe("PermissionGuard", () => {
  let guard: PermissionGuard;
  let reflector: { getAllAndOverride: ReturnType<typeof vi.fn> };

  function createMockContext(
    request: Partial<AuthenticatedRequest>,
  ): ExecutionContext {
    return {
      switchToHttp: () => ({
        getRequest: () => request as unknown as AuthenticatedRequest,
      }),
      getHandler: () => ({}),
      getClass: () => ({}),
    } as unknown as ExecutionContext;
  }

  beforeEach(() => {
    reflector = {
      getAllAndOverride: vi.fn(),
    };
    guard = new PermissionGuard(reflector as never);
  });

  it("should allow access when DEV_MODE is true", () => {
    const original = process.env.DEV_MODE;
    process.env.DEV_MODE = "true";
    const ctx = createMockContext({ userRoles: [] });
    expect(guard.canActivate(ctx)).toBe(true);
    process.env.DEV_MODE = original;
  });

  it("should allow access when no required permissions", () => {
    process.env.DEV_MODE = "false";
    reflector.getAllAndOverride.mockReturnValue(undefined);
    const ctx = createMockContext({ userRoles: [] });
    expect(guard.canActivate(ctx)).toBe(true);
  });

  it("should allow access when no required permissions (empty array)", () => {
    reflector.getAllAndOverride.mockReturnValue([]);
    const ctx = createMockContext({ userRoles: [] });
    expect(guard.canActivate(ctx)).toBe(true);
  });

  it("should allow access when user role has required permission", () => {
    reflector.getAllAndOverride.mockReturnValue([
      Permissions.CONTENT_TEXTBOOK_READ,
    ]);
    const ctx = createMockContext({
      userRoles: ["admin"],
    });
    expect(guard.canActivate(ctx)).toBe(true);
  });

  it("should allow access when teacher role has matching permission", () => {
    reflector.getAllAndOverride.mockReturnValue([
      Permissions.CONTENT_CHAPTER_CREATE,
    ]);
    const ctx = createMockContext({
      userRoles: ["teacher"],
    });
    expect(guard.canActivate(ctx)).toBe(true);
  });

  it("should allow access when at least one role matches", () => {
    reflector.getAllAndOverride.mockReturnValue([
      Permissions.CONTENT_TEXTBOOK_READ,
    ]);
    const ctx = createMockContext({
      userRoles: ["student", "unknown-role"],
    });
    expect(guard.canActivate(ctx)).toBe(true);
  });

  it("should throw PermissionDeniedError when user lacks permission", () => {
    reflector.getAllAndOverride.mockReturnValue([
      Permissions.CONTENT_TEXTBOOK_DELETE,
    ]);
    const ctx = createMockContext({
      userRoles: ["student"],
    });
    expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError);
  });

  it("should throw PermissionDeniedError when user has no roles", () => {
    reflector.getAllAndOverride.mockReturnValue([
      Permissions.CONTENT_TEXTBOOK_READ,
    ]);
    const ctx = createMockContext({
      userRoles: [],
    });
    expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError);
  });

  it("should use empty roles array when userRoles is undefined", () => {
    reflector.getAllAndOverride.mockReturnValue([
      Permissions.CONTENT_TEXTBOOK_READ,
    ]);
    const ctx = createMockContext({});
    expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError);
  });

  it("should expose PERMISSIONS_KEY constant", () => {
    expect(PERMISSIONS_KEY).toBe("permissions");
  });
});
