import 'reflect-metadata';

import { Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import request from 'supertest';
import { RaceController } from './RaceController';
import { RaceService } from './RaceService';
import { vi, Mocked } from 'vitest';
import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import type { AuthorizationService } from '../auth/AuthorizationService';
import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
import type { PolicyService, PolicySnapshot } from '../policy/PolicyService';

describe('RaceController', () => {
  let controller: RaceController;
  let service: Mocked<RaceService>;

  beforeEach(async () => {
    const mockService = {
      getAllRaces: vi.fn(),
      getTotalRaces: vi.fn(),
      getRacesPageData: vi.fn(),
      getAllRacesPageData: vi.fn(),
      getRaceDetail: vi.fn(),
      getRaceResultsDetail: vi.fn(),
      getRaceWithSOF: vi.fn(),
      getRaceProtests: vi.fn(),
      getRacePenalties: vi.fn(),
      registerForRace: vi.fn(),
      withdrawFromRace: vi.fn(),
      cancelRace: vi.fn(),
      completeRace: vi.fn(),
      importRaceResults: vi.fn(),
      fileProtest: vi.fn(),
      applyQuickPenalty: vi.fn(),
      applyPenalty: vi.fn(),
      requestProtestDefense: vi.fn(),
    } as unknown as Mocked<RaceService>;

    const module: TestingModule = await Test.createTestingModule({
      controllers: [RaceController],
      providers: [
        {
          provide: RaceService,
          useValue: mockService,
        },
      ],
    }).compile();

    controller = module.get<RaceController>(RaceController);
    service = module.get(RaceService) as Mocked<RaceService>;
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('getAllRaces', () => {
    it('should return all races view model', async () => {
      const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as any;
      service.getAllRaces.mockResolvedValue(mockPresenter);

      const result = await controller.getAllRaces();

      expect(service.getAllRaces).toHaveBeenCalled();
      expect(result).toEqual(mockPresenter.viewModel);
    });
  });

  describe('getTotalRaces', () => {
    it('should return total races count view model', async () => {
      const mockPresenter = { viewModel: { totalRaces: 5 } } as any;
      service.getTotalRaces.mockResolvedValue(mockPresenter);

      const result = await controller.getTotalRaces();

      expect(service.getTotalRaces).toHaveBeenCalled();
      expect(result).toEqual(mockPresenter.viewModel);
    });
  });

  describe('auth guards (HTTP)', () => {
    let app: any;

    const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
      getCurrentSession: vi.fn(async () => null),
    };

    const authorizationService: Pick<AuthorizationService, 'getRolesForUser'> = {
      getRolesForUser: vi.fn(() => []),
    };

    const policyService: Pick<PolicyService, 'getSnapshot'> = {
      getSnapshot: vi.fn(async (): Promise<PolicySnapshot> => ({
        policyVersion: 1,
        operationalMode: 'normal',
        maintenanceAllowlist: { view: [], mutate: [] },
        capabilities: {},
        loadedFrom: 'defaults',
        loadedAtIso: new Date(0).toISOString(),
      })),
    };

    beforeEach(async () => {
      const module = await Test.createTestingModule({
        controllers: [RaceController],
        providers: [
          {
            provide: RaceService,
            useValue: {
              getAllRaces: vi.fn(async () => ({ viewModel: { races: [], filters: { statuses: [], leagues: [] } } })),
              registerForRace: vi.fn(async () => ({ viewModel: { success: true } })),
            },
          },
        ],
      }).compile();

      app = module.createNestApplication();

      const reflector = new Reflector();
      app.useGlobalGuards(
        new AuthenticationGuard(sessionPort as any),
        new AuthorizationGuard(reflector, authorizationService as any),
        new FeatureAvailabilityGuard(reflector, policyService as any),
      );

      await app.init();
    });

    afterEach(async () => {
      await app?.close();
      vi.clearAllMocks();
    });

    it('allows @Public() read without a session', async () => {
      await request(app.getHttpServer()).get('/races/all').expect(200);
    });

    it('denies mutation by default when not authenticated (401)', async () => {
      await request(app.getHttpServer()).post('/races/r1/register').send({}).expect(401);
    });

    it('allows mutation when authenticated via session port', async () => {
      vi.mocked(sessionPort.getCurrentSession).mockResolvedValueOnce({
        token: 't',
        user: { id: 'user-1' },
      });

      await request(app.getHttpServer()).post('/races/r1/register').send({}).expect(200);
    });
  });
});