import { ForbiddenException, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { describe, expect, it, vi } from 'vitest';
import { AuthorizationGuard } from './AuthorizationGuard';
import { Public } from './Public';
import { RequireRoles } from './RequireRoles';

class DummyController {
  @Public()
  publicHandler(): void {}

  protectedHandler(): void {}

  @RequireRoles('admin')
  adminHandler(): void {}
}

function createExecutionContext(options: { handler: Function; userId?: string }) {
  const request = options.userId ? { user: { userId: options.userId } } : {};

  return {
    getHandler: () => options.handler,
    getClass: () => DummyController,
    switchToHttp: () => ({
      getRequest: () => request,
    }),
  };
}

describe('AuthorizationGuard', () => {
  it('allows public routes without a user session', async () => {
    const authorizationService = { getRolesForUser: vi.fn() };
    const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);

    const ctx = createExecutionContext({
      handler: DummyController.prototype.publicHandler,
    });

    await expect(guard.canActivate(ctx as never)).resolves.toBe(true);
    expect(authorizationService.getRolesForUser).not.toHaveBeenCalled();
  });

  it('denies non-public routes by default when not authenticated', async () => {
    const authorizationService = { getRolesForUser: vi.fn() };
    const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);

    const ctx = createExecutionContext({
      handler: DummyController.prototype.protectedHandler,
    });

    await expect(guard.canActivate(ctx as never)).rejects.toThrow(UnauthorizedException);
  });

  it('allows non-public routes when authenticated', async () => {
    const authorizationService = { getRolesForUser: vi.fn().mockReturnValue([]) };
    const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);

    const ctx = createExecutionContext({
      handler: DummyController.prototype.protectedHandler,
      userId: 'user-1',
    });

    await expect(guard.canActivate(ctx as never)).resolves.toBe(true);
  });

  it('denies routes requiring roles when user does not have any required role', async () => {
    const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['user']) };
    const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);

    const ctx = createExecutionContext({
      handler: DummyController.prototype.adminHandler,
      userId: 'user-1',
    });

    await expect(guard.canActivate(ctx as never)).rejects.toThrow(ForbiddenException);
  });

  it('allows routes requiring roles when user has a required role', async () => {
    const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['admin']) };
    const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);

    const ctx = createExecutionContext({
      handler: DummyController.prototype.adminHandler,
      userId: 'user-1',
    });

    await expect(guard.canActivate(ctx as never)).resolves.toBe(true);
  });
});