import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { Reflector } from '@nestjs/core';
import { ForbiddenException } from '@nestjs/common';
import { ModuleGuard } from './module.guard';
import { PrismaService } from '../../prisma/prisma.service';
import { REQUIRE_MODULE_KEY } from '../decorators/require-module.decorator';

describe('ModuleGuard', () => {
  let guard: ModuleGuard;
  let reflector: Reflector;
  let prisma: { $queryRawUnsafe: jest.Mock };

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

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

    prisma = {
      $queryRawUnsafe: jest.fn(),
    };

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

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

  it('should return true when no @RequireModule decorator is present', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue(undefined);
    const context = createMockExecutionContext({ clubId: 'club-001' });

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

    expect(result).toBe(true);
    expect(reflector.getAllAndOverride).toHaveBeenCalledWith(REQUIRE_MODULE_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    expect(prisma.$queryRawUnsafe).not.toHaveBeenCalled();
  });

  it('should return true when module is enabled for the club', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue('sip');
    prisma.$queryRawUnsafe.mockResolvedValue([{ enabled: true }]);
    const context = createMockExecutionContext({ clubId: 'club-001' });

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

    expect(result).toBe(true);
    expect(prisma.$queryRawUnsafe).toHaveBeenCalledWith(
      expect.stringContaining('SELECT enabled FROM club_modules'),
      'club-001',
      'sip',
    );
  });

  it('should throw ForbiddenException when no clubId in request', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue('sip');
    const context = createMockExecutionContext(undefined);

    await expect(guard.canActivate(context as any)).rejects.toThrow(ForbiddenException);
    await expect(guard.canActivate(context as any)).rejects.toThrow('Club context is required');
  });

  it('should throw ForbiddenException when clubId is missing from user object', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue('sip');
    const context = createMockExecutionContext({});

    await expect(guard.canActivate(context as any)).rejects.toThrow(ForbiddenException);
    await expect(guard.canActivate(context as any)).rejects.toThrow('Club context is required');
  });

  it('should throw ForbiddenException when module is not found in club_modules', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue('sip');
    prisma.$queryRawUnsafe.mockResolvedValue([]);
    const context = createMockExecutionContext({ clubId: 'club-001' });

    await expect(guard.canActivate(context as any)).rejects.toThrow(ForbiddenException);
    await expect(guard.canActivate(context as any)).rejects.toThrow(
      'Module "sip" is not enabled for this club',
    );
  });

  it('should throw ForbiddenException when module exists but enabled is false', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue('webhooks');
    prisma.$queryRawUnsafe.mockResolvedValue([{ enabled: false }]);
    const context = createMockExecutionContext({ clubId: 'club-002' });

    await expect(guard.canActivate(context as any)).rejects.toThrow(ForbiddenException);
    await expect(guard.canActivate(context as any)).rejects.toThrow(
      'Module "webhooks" is not enabled for this club',
    );
  });

  it('should query with correct clubId and moduleId parameters', async () => {
    (reflector.getAllAndOverride as jest.Mock).mockReturnValue('1c_sync');
    prisma.$queryRawUnsafe.mockResolvedValue([{ enabled: true }]);
    const context = createMockExecutionContext({ clubId: 'club-xyz-123' });

    await guard.canActivate(context as any);

    expect(prisma.$queryRawUnsafe).toHaveBeenCalledWith(
      expect.any(String),
      'club-xyz-123',
      '1c_sync',
    );
  });
});
