import { Test, TestingModule } from '@nestjs/testing';
import { Reflector } from '@nestjs/core';
import { ForbiddenException } from '@nestjs/common';
import { RolesGuard } from './roles.guard';
import { ROLES_KEY } from '../../../common/decorators/roles.decorator';

enum UserRole {
  TRAINER = 'trainer',
  COORDINATOR = 'coordinator',
  MANAGER = 'manager',
  RECEPTIONIST = 'receptionist',
  CLUB_ADMIN = 'club_admin',
  SUPER_ADMIN = 'super_admin',
}

describe('RolesGuard', () => {
  let guard: RolesGuard;
  let reflector: Reflector;

  const createMockExecutionContext = (user?: { role?: string }) => ({
    getHandler: jest.fn(),
    getClass: jest.fn(),
    switchToHttp: jest.fn().mockReturnValue({
      getRequest: jest.fn().mockReturnValue({ user }),
    }),
  });

  beforeEach(async () => {
    jest.clearAllMocks();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        RolesGuard,
        {
          provide: Reflector,
          useValue: {
            getAllAndOverride: jest.fn(),
          },
        },
      ],
    }).compile();

    guard = module.get<RolesGuard>(RolesGuard);
    reflector = module.get<Reflector>(Reflector);
  });

  it('should return true when no roles are required (no decorator)', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue(undefined);
    const context = createMockExecutionContext({ role: 'trainer' });

    const result = guard.canActivate(context as any);

    expect(result).toBe(true);
    expect(reflector.getAllAndOverride).toHaveBeenCalledWith(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
  });

  it('should return true when required roles array is empty', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([]);
    const context = createMockExecutionContext({ role: 'trainer' });

    const result = guard.canActivate(context as any);

    expect(result).toBe(true);
  });

  it.each([
    [UserRole.TRAINER, 'trainer'],
    [UserRole.COORDINATOR, 'coordinator'],
    [UserRole.MANAGER, 'manager'],
    [UserRole.RECEPTIONIST, 'receptionist'],
    [UserRole.CLUB_ADMIN, 'club_admin'],
    [UserRole.SUPER_ADMIN, 'super_admin'],
  ])('should return true when user role matches required role %s', (role, roleString) => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([role]);
    const context = createMockExecutionContext({ role: roleString });

    const result = guard.canActivate(context as any);

    expect(result).toBe(true);
  });

  it('should throw ForbiddenException when user role is not in required list', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([
      UserRole.COORDINATOR,
      UserRole.MANAGER,
    ]);
    const context = createMockExecutionContext({ role: 'trainer' });

    expect(() => guard.canActivate(context as any)).toThrow(ForbiddenException);
    expect(() => guard.canActivate(context as any)).toThrow(
      'Access denied. Required roles: coordinator, manager',
    );
  });

  it('should throw ForbiddenException when user has no role property', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([UserRole.TRAINER]);
    const context = createMockExecutionContext({});

    expect(() => guard.canActivate(context as any)).toThrow(ForbiddenException);
    expect(() => guard.canActivate(context as any)).toThrow('User role is not defined');
  });

  it('should throw ForbiddenException when there is no user object', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([UserRole.TRAINER]);
    const context = createMockExecutionContext(undefined);

    expect(() => guard.canActivate(context as any)).toThrow(ForbiddenException);
    expect(() => guard.canActivate(context as any)).toThrow('User role is not defined');
  });

  it('should return true when user has one of multiple required roles', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([
      UserRole.TRAINER,
      UserRole.COORDINATOR,
      UserRole.MANAGER,
    ]);
    const context = createMockExecutionContext({ role: 'coordinator' });

    const result = guard.canActivate(context as any);

    expect(result).toBe(true);
  });

  it('should return true when super_admin matches in a multi-role requirement', () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue([
      UserRole.CLUB_ADMIN,
      UserRole.SUPER_ADMIN,
    ]);
    const context = createMockExecutionContext({ role: 'super_admin' });

    const result = guard.canActivate(context as any);

    expect(result).toBe(true);
  });

  it('should throw ForbiddenException with all required roles listed in message', () => {
    const requiredRoles = [UserRole.MANAGER, UserRole.CLUB_ADMIN, UserRole.SUPER_ADMIN];
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue(requiredRoles);
    const context = createMockExecutionContext({ role: 'trainer' });

    expect(() => guard.canActivate(context as any)).toThrow(
      'Access denied. Required roles: manager, club_admin, super_admin',
    );
  });
});
