import 'reflect-metadata';

import { ValidationPipe } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { AuthenticationGuard } from '../auth/AuthenticationGuard';
import { AuthorizationGuard } from '../auth/AuthorizationGuard';
import type { AuthorizationService } from '../auth/AuthorizationService';
import { LeagueModule } from './LeagueModule';

import { requestContextMiddleware } from '@adapters/http/RequestContext';

import {
    DRIVER_REPOSITORY_TOKEN,
    LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN,
    LEAGUE_REPOSITORY_TOKEN,
    SEASON_REPOSITORY_TOKEN,
    LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN,
} from '../../persistence/inmemory/InMemoryRacingPersistenceModule';

import { Driver } from '@core/racing/domain/entities/Driver';
import { League } from '@core/racing/domain/entities/League';
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import { Season } from '@core/racing/domain/entities/season/Season';
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';

describe('League Config API - Integration', () => {
  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(() => []),
  };

  async function seedLeagueWithConfig(params: {
    leagueId: string;
    ownerId: string;
    seasonId: string;
    scoringConfigId: string;
    gameId: string;
  }): Promise<void> {
    const leagueRepo = app.get(LEAGUE_REPOSITORY_TOKEN);
    const driverRepo = app.get(DRIVER_REPOSITORY_TOKEN);
    const membershipRepo = app.get(LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN);
    const seasonRepo = app.get(SEASON_REPOSITORY_TOKEN);
    const scoringRepo = app.get(LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN);

    // Game is seeded by default, no need to create

    // Create league
    await leagueRepo.create(
      League.create({
        id: params.leagueId,
        name: 'Test League',
        description: 'Test league',
        ownerId: params.ownerId,
        settings: { visibility: 'ranked' },
      }),
    );

    // Create owner driver
    await driverRepo.create(
      Driver.create({
        id: params.ownerId,
        iracingId: '2001',
        name: 'Owner Driver',
        country: 'DE',
      }),
    );

    // Create membership
    await membershipRepo.saveMembership(
      LeagueMembership.create({
        leagueId: params.leagueId,
        driverId: params.ownerId,
        role: 'owner',
        status: 'active',
      }),
    );

    // Create season
    await seasonRepo.create(
      Season.create({
        id: params.seasonId,
        leagueId: params.leagueId,
        gameId: params.gameId,
        name: 'Test Season',
        startDate: new Date('2025-01-01'),
        endDate: new Date('2025-12-31'),
        status: 'active',
      }),
    );

    // Create scoring config
    await scoringRepo.save(
      LeagueScoringConfig.create({
        id: params.scoringConfigId,
        seasonId: params.seasonId,
        scoringPresetId: 'f1-2024',
        championships: [
          {
            id: 'championship-1',
            name: 'Main Championship',
            type: 'driver',
            sessionTypes: ['main'],
            pointsTableBySessionType: {
              practice: new PointsTable({}),
              qualifying: new PointsTable({}),
              q1: new PointsTable({}),
              q2: new PointsTable({}),
              q3: new PointsTable({}),
              sprint: new PointsTable({}),
              main: new PointsTable({ 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1 }),
              timeTrial: new PointsTable({}),
            },
            dropScorePolicy: { strategy: 'none' },
          },
        ],
      }),
    );
  }

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [LeagueModule],
    }).compile();

    app = module.createNestApplication();

    // Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner().
    app.use(requestContextMiddleware as never);

    // Test-only auth injection: emulate an authenticated session by setting request.user.
    app.use((req: unknown, _res: unknown, next: unknown) => {
      const r = req as { headers: Record<string, string>, user?: { userId: string } };
      const userId = r.headers['x-test-user-id'];
      if (typeof userId === 'string' && userId.length > 0) {
        r.user = { userId };
      }
      (next as () => void)();
    });

    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );

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

    await app.init();
  });

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

  it('should return league config with all dependencies injected', async () => {
    const leagueId = 'league-1';
    const ownerId = 'owner-1';
    const seasonId = 'season-1';
    const scoringConfigId = 'scoring-1';
    const gameId = 'game-1';

    await seedLeagueWithConfig({ leagueId, ownerId, seasonId, scoringConfigId, gameId });

    const response = await request(app.getHttpServer())
      .get(`/leagues/${leagueId}/config`)
      .expect(200);

    // Verify the response contains expected data
    expect(response.body).toBeDefined();
    expect(response.body.leagueId).toBeDefined();
    expect(response.body.leagueId.value).toBe(leagueId);
    expect(response.body.basics).toBeDefined();
    expect(response.body.basics.name.value).toBe('Test League');
    expect(response.body.basics.visibility).toBe('ranked');
    expect(response.body.scoring).toBeDefined();
    expect(response.body.scoring.type).toBe('f1-2024');
  });

  it('should return null for non-existent league', async () => {
    const response = await request(app.getHttpServer())
      .get('/leagues/non-existent-league/config')
      .expect(200);

    // NestJS serializes null to {} when returning from controller
    expect(response.body).toEqual({});
  });

  it('should handle league with no season or scoring config', async () => {
    const leagueId = 'league-2';
    const ownerId = 'owner-2';

    const leagueRepo = app.get(LEAGUE_REPOSITORY_TOKEN);
    const driverRepo = app.get(DRIVER_REPOSITORY_TOKEN);
    const membershipRepo = app.get(LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN);

    // Create league without season or scoring config
    await leagueRepo.create(
      League.create({
        id: leagueId,
        name: 'League Without Config',
        description: 'Test league',
        ownerId: ownerId,
        settings: { visibility: 'unranked' },
      }),
    );

    await driverRepo.create(
      Driver.create({
        id: ownerId,
        iracingId: '2002',
        name: 'Owner Driver 2',
        country: 'US',
      }),
    );

    await membershipRepo.saveMembership(
      LeagueMembership.create({
        leagueId: leagueId,
        driverId: ownerId,
        role: 'owner',
        status: 'active',
      }),
    );

    const response = await request(app.getHttpServer())
      .get(`/leagues/${leagueId}/config`)
      .expect(200);

    // Should still return basic league info
    expect(response.body).toBeDefined();
    expect(response.body.leagueId).toBeDefined();
    expect(response.body.leagueId.value).toBe(leagueId);
    expect(response.body.basics).toBeDefined();
    expect(response.body.basics.name.value).toBe('League Without Config');
    expect(response.body.basics.visibility).toBe('unranked');
  });
});
