import { describe, it, expect } from 'vitest';

import {
  InMemorySeasonRepository,
} from '@core/racing/infrastructure/repositories/InMemoryScoringRepositories';
import { Season } from '@core/racing/domain/entities/season/Season';
import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeasonRepository';
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
import {
  CreateSeasonForLeagueUseCase,
  ListSeasonsForLeagueUseCase,
  GetSeasonDetailsUseCase,
  ManageSeasonLifecycleUseCase,
  type CreateSeasonForLeagueCommand,
  type ManageSeasonLifecycleCommand,
} from '@core/racing/application/use-cases/SeasonUseCases';
import type { LeagueConfigFormModel } from '@core/racing/application/dto/LeagueConfigFormDTO';

function createFakeLeagueRepository(seed: Array<{ id: string }>): ILeagueRepository {
  return {
    findById: async (id: string) => seed.find((l) => l.id === id) ?? null,
    findAll: async () => seed,
    create: async (league: any) => league,
    update: async (league: any) => league,
  } as unknown as ILeagueRepository;
}

function createLeagueConfigFormModel(overrides?: Partial<LeagueConfigFormModel>): LeagueConfigFormModel {
  return {
    basics: {
      name: 'Test League',
      visibility: 'ranked',
      gameId: 'iracing',
      ...overrides?.basics,
    },
    structure: {
      mode: 'solo',
      maxDrivers: 30,
      ...overrides?.structure,
    },
    championships: {
      enableDriverChampionship: true,
      enableTeamChampionship: false,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
      ...overrides?.championships,
    },
    scoring: {
      patternId: 'sprint-main-driver',
      customScoringEnabled: false,
      ...overrides?.scoring,
    },
    dropPolicy: {
      strategy: 'bestNResults',
      n: 3,
      ...overrides?.dropPolicy,
    },
    timings: {
      qualifyingMinutes: 10,
      mainRaceMinutes: 30,
      sessionCount: 8,
      seasonStartDate: '2025-01-01',
      raceStartTime: '20:00',
      timezoneId: 'UTC',
      recurrenceStrategy: 'weekly',
      weekdays: ['Mon'],
      ...overrides?.timings,
    },
    stewarding: {
      decisionMode: 'steward_vote',
      requiredVotes: 3,
      requireDefense: true,
      defenseTimeLimit: 24,
      voteTimeLimit: 24,
      protestDeadlineHours: 48,
      stewardingClosesHours: 72,
      notifyAccusedOnProtest: true,
      notifyOnVoteRequired: true,
      ...overrides?.stewarding,
    },
    ...overrides,
  };
}

describe('InMemorySeasonRepository', () => {
  it('add and findById provide a roundtrip for Season', async () => {
    const repo = new InMemorySeasonRepository();
    const season = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Test Season',
      status: 'planned',
    });

    await repo.add(season);
    const loaded = await repo.findById(season.id);

    expect(loaded).not.toBeNull();
    expect(loaded!.id).toBe(season.id);
    expect(loaded!.leagueId).toBe(season.leagueId);
    expect(loaded!.status).toBe('planned');
  });

  it('update persists changed Season state', async () => {
    const repo = new InMemorySeasonRepository();
    const season = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Initial Season',
      status: 'planned',
    });

    await repo.add(season);
    const activated = season.activate();

    await repo.update(activated);

    const loaded = await repo.findById(season.id);
    expect(loaded).not.toBeNull();
    expect(loaded!.status).toBe('active');
  });

  it('listByLeague returns only seasons for that league', async () => {
    const repo = new InMemorySeasonRepository();
    const s1 = Season.create({
      id: 's1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'L1 S1',
      status: 'planned',
    });
    const s2 = Season.create({
      id: 's2',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'L1 S2',
      status: 'active',
    });
    const s3 = Season.create({
      id: 's3',
      leagueId: 'league-2',
      gameId: 'iracing',
      name: 'L2 S1',
      status: 'planned',
    });

    await repo.add(s1);
    await repo.add(s2);
    await repo.add(s3);

    const league1Seasons = await repo.listByLeague('league-1');
    const league2Seasons = await repo.listByLeague('league-2');

    expect(league1Seasons.map((s) => s.id).sort()).toEqual(['s1', 's2']);
    expect(league2Seasons.map((s) => s.id)).toEqual(['s3']);
  });

  it('listActiveByLeague returns only active seasons for a league', async () => {
    const repo = new InMemorySeasonRepository();
    const s1 = Season.create({
      id: 's1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Planned',
      status: 'planned',
    });
    const s2 = Season.create({
      id: 's2',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Active',
      status: 'active',
    });
    const s3 = Season.create({
      id: 's3',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Completed',
      status: 'completed',
    });
    const s4 = Season.create({
      id: 's4',
      leagueId: 'league-2',
      gameId: 'iracing',
      name: 'Other League Active',
      status: 'active',
    });

    await repo.add(s1);
    await repo.add(s2);
    await repo.add(s3);
    await repo.add(s4);

    const activeInLeague1 = await repo.listActiveByLeague('league-1');

    expect(activeInLeague1.map((s) => s.id)).toEqual(['s2']);
  });
});

describe('CreateSeasonForLeagueUseCase', () => {
  it('creates a planned Season for an existing league with config-derived props', async () => {
    const leagueRepo = createFakeLeagueRepository([{ id: 'league-1' }]);
    const seasonRepo = new InMemorySeasonRepository();

    const useCase = new CreateSeasonForLeagueUseCase(leagueRepo, seasonRepo);

    const config = createLeagueConfigFormModel({
      basics: {
        name: 'League With Config',
        visibility: 'ranked',
        gameId: 'iracing',
      },
      scoring: {
        patternId: 'club-default',
        customScoringEnabled: true,
      },
      dropPolicy: {
        strategy: 'dropWorstN',
        n: 2,
      },
      // Intentionally omit seasonStartDate / raceStartTime to avoid schedule derivation,
      // focusing this test on scoring/drop/stewarding/maxDrivers mapping.
      timings: {
        qualifyingMinutes: 10,
        mainRaceMinutes: 30,
        sessionCount: 8,
      },
    });

    const command: CreateSeasonForLeagueCommand = {
      leagueId: 'league-1',
      name: 'Season from Config',
      gameId: 'iracing',
      config,
    };

    const result = await useCase.execute(command);

    expect(result.seasonId).toBeDefined();

    const created = await seasonRepo.findById(result.seasonId);
    expect(created).not.toBeNull();
    const season = created!;

    expect(season.leagueId).toBe('league-1');
    expect(season.gameId).toBe('iracing');
    expect(season.name).toBe('Season from Config');
    expect(season.status).toBe('planned');

    // Schedule is optional when timings lack seasonStartDate / raceStartTime.
    expect(season.schedule).toBeUndefined();
    expect(season.scoringConfig).toBeDefined();
    expect(season.scoringConfig!.scoringPresetId).toBe('club-default');
    expect(season.scoringConfig!.customScoringEnabled).toBe(true);

    expect(season.dropPolicy).toBeDefined();
    expect(season.dropPolicy!.strategy).toBe('dropWorstN');
    expect(season.dropPolicy!.n).toBe(2);

    expect(season.stewardingConfig).toBeDefined();
    expect(season.maxDrivers).toBe(30);
  });

  it('clones configuration from a source season when sourceSeasonId is provided', async () => {
    const leagueRepo = createFakeLeagueRepository([{ id: 'league-1' }]);
    const seasonRepo = new InMemorySeasonRepository();

    const sourceSeason = Season.create({
      id: 'source-season',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Source Season',
      status: 'planned',
    }).withMaxDrivers(40);

    await seasonRepo.add(sourceSeason);

    const useCase = new CreateSeasonForLeagueUseCase(leagueRepo, seasonRepo);

    const command: CreateSeasonForLeagueCommand = {
      leagueId: 'league-1',
      name: 'Cloned Season',
      gameId: 'iracing',
      sourceSeasonId: 'source-season',
    };

    const result = await useCase.execute(command);
    const created = await seasonRepo.findById(result.seasonId);

    expect(created).not.toBeNull();
    const season = created!;

    expect(season.id).not.toBe(sourceSeason.id);
    expect(season.leagueId).toBe(sourceSeason.leagueId);
    expect(season.gameId).toBe(sourceSeason.gameId);
    expect(season.status).toBe('planned');
    expect(season.maxDrivers).toBe(sourceSeason.maxDrivers);
    expect(season.schedule).toBe(sourceSeason.schedule);
    expect(season.scoringConfig).toBe(sourceSeason.scoringConfig);
    expect(season.dropPolicy).toBe(sourceSeason.dropPolicy);
    expect(season.stewardingConfig).toBe(sourceSeason.stewardingConfig);
  });
});

describe('ListSeasonsForLeagueUseCase', () => {
  it('lists seasons for a league with summaries', async () => {
    const leagueRepo = createFakeLeagueRepository([{ id: 'league-1' }]);
    const seasonRepo = new InMemorySeasonRepository();

    const s1 = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Season One',
      status: 'planned',
    });
    const s2 = Season.create({
      id: 'season-2',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Season Two',
      status: 'active',
    });
    const sOtherLeague = Season.create({
      id: 'season-3',
      leagueId: 'league-2',
      gameId: 'iracing',
      name: 'Season Other',
      status: 'planned',
    });

    await seasonRepo.add(s1);
    await seasonRepo.add(s2);
    await seasonRepo.add(sOtherLeague);

    const useCase = new ListSeasonsForLeagueUseCase(leagueRepo, seasonRepo);

    const result = await useCase.execute({ leagueId: 'league-1' });

    expect(result.items.map((i) => i.seasonId).sort()).toEqual([
      'season-1',
      'season-2',
    ]);
    expect(result.items.every((i) => i.leagueId === 'league-1')).toBe(true);
  });
});

describe('GetSeasonDetailsUseCase', () => {
  it('returns full details for a season belonging to the league', async () => {
    const leagueRepo = createFakeLeagueRepository([{ id: 'league-1' }]);
    const seasonRepo = new InMemorySeasonRepository();

    const season = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Detailed Season',
      status: 'planned',
    }).withMaxDrivers(24);

    await seasonRepo.add(season);

    const useCase = new GetSeasonDetailsUseCase(leagueRepo, seasonRepo);

    const dto = await useCase.execute({
      leagueId: 'league-1',
      seasonId: 'season-1',
    });

    expect(dto.seasonId).toBe('season-1');
    expect(dto.leagueId).toBe('league-1');
    expect(dto.gameId).toBe('iracing');
    expect(dto.name).toBe('Detailed Season');
    expect(dto.status).toBe('planned');
    expect(dto.maxDrivers).toBe(24);
  });
});

describe('ManageSeasonLifecycleUseCase', () => {
  function setupLifecycleTest() {
    const leagueRepo = createFakeLeagueRepository([{ id: 'league-1' }]);
    const seasonRepo = new InMemorySeasonRepository();

    const season = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'iracing',
      name: 'Lifecycle Season',
      status: 'planned',
    });

    seasonRepo.seed(season);

    const useCase = new ManageSeasonLifecycleUseCase(leagueRepo, seasonRepo);

    return { leagueRepo, seasonRepo, useCase, season };
  }

  it('applies activate → complete → archive transitions and persists state', async () => {
    const { useCase, seasonRepo, season } = setupLifecycleTest();

    const activateCommand: ManageSeasonLifecycleCommand = {
      leagueId: 'league-1',
      seasonId: season.id,
      transition: 'activate',
    };

    const activated = await useCase.execute(activateCommand);
    expect(activated.status).toBe('active');

    const completeCommand: ManageSeasonLifecycleCommand = {
      leagueId: 'league-1',
      seasonId: season.id,
      transition: 'complete',
    };

    const completed = await useCase.execute(completeCommand);
    expect(completed.status).toBe('completed');

    const archiveCommand: ManageSeasonLifecycleCommand = {
      leagueId: 'league-1',
      seasonId: season.id,
      transition: 'archive',
    };

    const archived = await useCase.execute(archiveCommand);
    expect(archived.status).toBe('archived');

    const persisted = await seasonRepo.findById(season.id);
    expect(persisted!.status).toBe('archived');
  });

  it('propagates domain invariant errors for invalid transitions', async () => {
    const { useCase, seasonRepo, season } = setupLifecycleTest();

    const completeCommand: ManageSeasonLifecycleCommand = {
      leagueId: 'league-1',
      seasonId: season.id,
      transition: 'complete',
    };

    await expect(useCase.execute(completeCommand)).rejects.toThrow();

    const persisted = await seasonRepo.findById(season.id);
    expect(persisted!.status).toBe('planned');
  });
});