import { ForbiddenException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { vi, type MockedFunction } from 'vitest';
import { ReviewProtestCommandDTO } from '../race/dtos/ReviewProtestCommandDTO';
import type { ReviewProtestResponseDTO } from './presenters/ReviewProtestPresenter';
import { ProtestsController } from './ProtestsController';
import { ProtestsService } from './ProtestsService';

describe('ProtestsController', () => {
  let controller: ProtestsController;
  let reviewProtestMock: MockedFunction<ProtestsService['reviewProtest']>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [ProtestsController],
      providers: [
        {
          provide: ProtestsService,
          useValue: {
            reviewProtest: vi.fn(),
          },
        },
      ],
    }).compile();

    controller = module.get<ProtestsController>(ProtestsController);
    const service = module.get(ProtestsService);
    reviewProtestMock = vi.mocked(service.reviewProtest);
  });

  const successDto = (dto: ReviewProtestResponseDTO): ReviewProtestResponseDTO => dto;

  describe('reviewProtest', () => {
    const stewardUserId = 'steward-1';
    const req = { user: { userId: stewardUserId } };

    it('should call service and not throw on success', async () => {
      const protestId = 'protest-123';
      const body: Omit<ReviewProtestCommandDTO, 'protestId' | 'stewardId'> = {
        decision: 'uphold',
        decisionNotes: 'Reason',
      };

      reviewProtestMock.mockResolvedValue(
        successDto({
          success: true,
          protestId,
          stewardId: stewardUserId,
          decision: body.decision,
        }),
      );

      await controller.reviewProtest(protestId, body, req);

      expect(reviewProtestMock).toHaveBeenCalledWith({ protestId, stewardId: stewardUserId, ...body });
    });

    it('should throw NotFoundException when protest is not found', async () => {
      const protestId = 'protest-123';
      const body: Omit<ReviewProtestCommandDTO, 'protestId' | 'stewardId'> = {
        decision: 'uphold',
        decisionNotes: 'Reason',
      };

      reviewProtestMock.mockResolvedValue(
        successDto({
          success: false,
          errorCode: 'PROTEST_NOT_FOUND',
          message: 'Protest not found',
        }),
      );

      await expect(controller.reviewProtest(protestId, body, req)).rejects.toBeInstanceOf(NotFoundException);
    });

    it('should throw ForbiddenException when steward is not league admin', async () => {
      const protestId = 'protest-123';
      const body: Omit<ReviewProtestCommandDTO, 'protestId' | 'stewardId'> = {
        decision: 'uphold',
        decisionNotes: 'Reason',
      };

      reviewProtestMock.mockResolvedValue(
        successDto({
          success: false,
          errorCode: 'NOT_LEAGUE_ADMIN',
          message: 'Not authorized',
        }),
      );

      await expect(controller.reviewProtest(protestId, body, req)).rejects.toBeInstanceOf(ForbiddenException);
    });

    it('should throw InternalServerErrorException for unexpected error codes', async () => {
      const protestId = 'protest-123';
      const body: Omit<ReviewProtestCommandDTO, 'protestId' | 'stewardId'> = {
        decision: 'uphold',
        decisionNotes: 'Reason',
      };

      reviewProtestMock.mockResolvedValue(
        successDto({
          success: false,
          errorCode: 'UNEXPECTED_ERROR',
          message: 'Unexpected',
        }),
      );

      await expect(controller.reviewProtest(protestId, body, req)).rejects.toBeInstanceOf(InternalServerErrorException);
    });
  });
});
