import 'reflect-metadata';

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { DataSource } from 'typeorm';

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

import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';

import { LeagueOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/LeagueOrmEntity';
import { LeagueScoringConfigOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/LeagueScoringConfigOrmEntity';
import { RaceOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/RaceOrmEntity';
import { SeasonOrmEntity } from '../../../../../../adapters/racing/persistence/typeorm/entities/SeasonOrmEntity';

import { TypeOrmLeagueRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueRepository';
import { TypeOrmLeagueScoringConfigRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmLeagueScoringConfigRepository';
import { TypeOrmRaceRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmRaceRepository';
import { TypeOrmSeasonRepository } from '../../../../../../adapters/racing/persistence/typeorm/repositories/TypeOrmSeasonRepository';
import { LeagueOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/LeagueOrmMapper';
import { RaceOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/RaceOrmMapper';
import { SeasonOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/SeasonOrmMapper';
import { PointsTableJsonMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/PointsTableJsonMapper';
import { ChampionshipConfigJsonMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/ChampionshipConfigJsonMapper';
import { LeagueScoringConfigOrmMapper } from '../../../../../../adapters/racing/persistence/typeorm/mappers/LeagueScoringConfigOrmMapper';

const databaseUrl = process.env.DATABASE_URL;
const describeIfDatabase = databaseUrl ? describe : describe.skip;

describeIfDatabase('TypeORM Racing repositories (postgres slice)', () => {
  let dataSource: DataSource;

  beforeAll(async () => {
    if (!databaseUrl) {
      throw new Error('DATABASE_URL is required to run postgres integration tests');
    }

    dataSource = new DataSource({
      type: 'postgres',
      url: databaseUrl,
      entities: [LeagueOrmEntity, SeasonOrmEntity, RaceOrmEntity, LeagueScoringConfigOrmEntity],
      synchronize: true,
    });

    await dataSource.initialize();
  });

  afterAll(async () => {
    if (dataSource?.isInitialized) {
      await dataSource.destroy();
    }
  });

  it('supports: create league + create season + save scoring config + fetch schedule', async () => {
    const leagueRepo = new TypeOrmLeagueRepository(dataSource, new LeagueOrmMapper());
    const seasonRepo = new TypeOrmSeasonRepository(dataSource, new SeasonOrmMapper());
    const raceRepo = new TypeOrmRaceRepository(dataSource, new RaceOrmMapper());

    const pointsTableMapper = new PointsTableJsonMapper();
    const championshipMapper = new ChampionshipConfigJsonMapper(pointsTableMapper);
    const scoringConfigMapper = new LeagueScoringConfigOrmMapper(championshipMapper);
    const scoringRepo = new TypeOrmLeagueScoringConfigRepository(dataSource, scoringConfigMapper);

    const league = League.create({
      id: 'league-it-1',
      name: 'Integration League',
      description: 'For integration testing',
      ownerId: 'driver-it-1',
      settings: { pointsSystem: 'custom', visibility: 'unranked', maxDrivers: 32 },
      participantCount: 0,
    });

    await leagueRepo.create(league);

    const season = Season.create({
      id: 'season-it-1',
      leagueId: league.id.toString(),
      gameId: 'iracing',
      name: 'Integration Season',
      year: 2025,
      order: 1,
      status: 'active',
      startDate: new Date('2025-01-01T00:00:00.000Z'),
      endDate: new Date('2025-12-31T00:00:00.000Z'),
      schedulePublished: false,
    });

    await seasonRepo.create(season);

    const pointsTableBySessionType: Record<SessionType, PointsTable> = {
      practice: new PointsTable({}),
      qualifying: new PointsTable({}),
      q1: new PointsTable({}),
      q2: new PointsTable({}),
      q3: new PointsTable({}),
      sprint: new PointsTable({}),
      main: new PointsTable({ 1: 25 }),
      timeTrial: new PointsTable({}),
    };

    const bonusRulesBySessionType = {
      practice: [],
      qualifying: [],
      q1: [],
      q2: [],
      q3: [],
      sprint: [],
      main: [],
      timeTrial: [],
    };

    const championship: ChampionshipConfig = {
      id: 'champ-it-1',
      name: 'Driver Championship',
      type: 'driver',
      sessionTypes: ['main' as SessionType],
      pointsTableBySessionType,
      bonusRulesBySessionType,
      dropScorePolicy: { strategy: 'none' },
    };

    const scoring = LeagueScoringConfig.create({
      id: 'lsc-it-1',
      seasonId: season.id,
      scoringPresetId: 'club-default',
      championships: [championship],
    });

    await scoringRepo.save(scoring);

    const race = Race.create({
      id: 'race-it-1',
      leagueId: league.id.toString(),
      scheduledAt: new Date('2025-03-01T12:00:00.000Z'),
      track: 'Spa',
      car: 'GT3',
      status: 'scheduled',
    });

    await raceRepo.create(race);

    const persistedLeague = await leagueRepo.findById(league.id.toString());
    expect(persistedLeague?.name.toString()).toBe('Integration League');

    const seasons = await seasonRepo.findByLeagueId(league.id.toString());
    expect(seasons.map((s: Season) => s.id)).toContain('season-it-1');

    const races = await raceRepo.findByLeagueId(league.id.toString());
    expect(races.map((r: Race) => r.id)).toContain('race-it-1');

    const persistedScoring = await scoringRepo.findBySeasonId(season.id);
    expect(persistedScoring?.id.toString()).toBe('lsc-it-1');
  });
});