import 'reflect-metadata';

import { Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import request from 'supertest';
import { vi } 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';
import { LeagueController } from './LeagueController';
import { LeagueService } from './LeagueService';

describe('LeagueController', () => {
  let controller: LeagueController;
  let leagueService: ReturnType<typeof vi.mocked<LeagueService>>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [LeagueController],
      providers: [
        {
          provide: LeagueService,
          useValue: {
            getTotalLeagues: vi.fn(),
            getAllLeaguesWithCapacity: vi.fn(),
            getLeagueStandings: vi.fn(),
          },
        },
      ],
    }).compile();

    controller = module.get<LeagueController>(LeagueController);
    leagueService = vi.mocked(module.get(LeagueService));
  });

  it('getTotalLeagues should return total leagues', async () => {
    const mockResult = { totalLeagues: 1 };
    leagueService.getTotalLeagues.mockResolvedValue(mockResult as never);

    const result = await controller.getTotalLeagues();

    expect(result).toEqual(mockResult);
    expect(leagueService.getTotalLeagues).toHaveBeenCalledTimes(1);
  });

  it('getAllLeaguesWithCapacity should return leagues and totalCount', async () => {
    const mockResult = { leagues: [], totalCount: 0 };
    leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as never);

    const result = await controller.getAllLeaguesWithCapacity();

    expect(result).toEqual(mockResult);
    expect(leagueService.getAllLeaguesWithCapacity).toHaveBeenCalledTimes(1);
  });

  it('getLeagueStandings should return standings', async () => {
    const mockResult = { standings: [] };
    leagueService.getLeagueStandings.mockResolvedValue(mockResult as never);

    const result = await controller.getLeagueStandings('league-1');

    expect(result).toEqual(mockResult);
    expect(leagueService.getLeagueStandings).toHaveBeenCalledWith('league-1');
  });

  describe('auth guards (HTTP)', () => {
    let app: import("@nestjs/common").INestApplication;

    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: [LeagueController],
        providers: [
          {
            provide: LeagueService,
            useValue: {
              getTotalLeagues: vi.fn(async () => ({ totalLeagues: 0 })),
              getLeagueAdmin: vi.fn(async () => ({}) ),
            },
          },
        ],
      }).compile();

      app = module.createNestApplication();

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

      await app.init();
    });

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

    it('allows @Public() endpoint without a session', async () => {
      await request(app.getHttpServer()).get('/leagues/total-leagues').expect(200);
    });

    it('denies non-public endpoint by default when not authenticated (401)', async () => {
      await request(app.getHttpServer()).get('/leagues/l1/admin').expect(401);
    });

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

      await request(app.getHttpServer()).get('/leagues/l1/admin').expect(200);
    });
  });

  describe('transfer ownership contract (HTTP)', () => {
    it('rejects client-supplied currentOwnerId (400) once DTO whitelisting is enforced', async () => {
      const leagueService = {
        transferLeagueOwnership: vi.fn(async () => ({ success: true })),
      };

      const module = await Test.createTestingModule({
        controllers: [LeagueController],
        providers: [{ provide: LeagueService, useValue: leagueService }],
      }).compile();

      const app = module.createNestApplication();
      await app.init();

      try {
        await request(app.getHttpServer())
          .post('/leagues/l1/transfer-ownership')
          .send({ currentOwnerId: 'spoof', newOwnerId: 'o2' })
          .expect(400);
      } finally {
        await app.close();
      }
    });
  });
});