import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
  GetLeagueFullConfigUseCase,
  type GetLeagueFullConfigInput,
  type GetLeagueFullConfigResult,
  type GetLeagueFullConfigErrorCode,
} from './GetLeagueFullConfigUseCase';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { IGameRepository } from '../../domain/repositories/IGameRepository';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

describe('GetLeagueFullConfigUseCase', () => {
  let useCase: GetLeagueFullConfigUseCase;
  let leagueRepository: ILeagueRepository & { findById: ReturnType<typeof vi.fn> };
  let seasonRepository: ISeasonRepository & { findByLeagueId: ReturnType<typeof vi.fn> };
  let leagueScoringConfigRepository: ILeagueScoringConfigRepository & {
    findBySeasonId: ReturnType<typeof vi.fn>;
  };
  let gameRepository: IGameRepository & { findById: ReturnType<typeof vi.fn> };
  let output: UseCaseOutputPort<GetLeagueFullConfigResult> & { present: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    leagueRepository = {
      findById: vi.fn(),
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } as any;
    seasonRepository = {
      findByLeagueId: vi.fn(),
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } as any;
    leagueScoringConfigRepository = {
      findBySeasonId: vi.fn(),
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } as any;
    gameRepository = {
      findById: vi.fn(),
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    } as any;

    output = { present: vi.fn() } as unknown as UseCaseOutputPort<GetLeagueFullConfigResult> & {
      present: ReturnType<typeof vi.fn>;
    };

    useCase = new GetLeagueFullConfigUseCase(
      leagueRepository,
      seasonRepository,
      leagueScoringConfigRepository,
      gameRepository,
      output,
    );
  });

  it('should return league config when league exists', async () => {
    const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };

    const mockLeague = {
      id: 'league-1',
      name: 'Test League',
      description: 'A test league',
      settings: {
        maxDrivers: 32,
        stewarding: {
          decisionMode: 'admin_only',
          requireDefense: false,
          defenseTimeLimit: 48,
          voteTimeLimit: 72,
          protestDeadlineHours: 48,
          stewardingClosesHours: 168,
          notifyAccusedOnProtest: true,
          notifyOnVoteRequired: true,
        },
      },
    };
    const mockSeasons = [{ id: 'season-1', status: { isActive: () => true }, gameId: 'game-1' }];
    const mockScoringConfig = { id: 'config-1' };
    const mockGame = { id: 'game-1' };

    leagueRepository.findById.mockResolvedValue(mockLeague);
    seasonRepository.findByLeagueId.mockResolvedValue(mockSeasons);
    leagueScoringConfigRepository.findBySeasonId.mockResolvedValue(mockScoringConfig);
    gameRepository.findById.mockResolvedValue(mockGame);

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();

    expect(output.present).toHaveBeenCalledTimes(1);
    const firstCall = output.present.mock.calls[0]!;
    const presented = firstCall[0] as GetLeagueFullConfigResult;

    expect(presented.config.league).toEqual(mockLeague);
    expect(presented.config.activeSeason).toEqual(mockSeasons[0]);
    expect(presented.config.scoringConfig).toEqual(mockScoringConfig);
    expect(presented.config.game).toEqual(mockGame);
  });

  it('should return error when league not found and not call presenter', async () => {
    const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };

    leagueRepository.findById.mockResolvedValue(null);

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueFullConfigErrorCode,
      { message: string }
    >;

    expect(error.code).toBe('LEAGUE_NOT_FOUND');
    expect(error.details.message).toBe('League not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should handle no active season', async () => {
    const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };

    const mockLeague = {
      id: 'league-1',
      name: 'Test League',
      description: 'A test league',
      settings: { maxDrivers: 32 },
    };

    leagueRepository.findById.mockResolvedValue(mockLeague);
    seasonRepository.findByLeagueId.mockResolvedValue([]);

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(output.present).toHaveBeenCalledTimes(1);

    const firstCall = output.present.mock.calls[0]!;
    const presented = firstCall[0] as GetLeagueFullConfigResult;

    expect(presented.config.league).toEqual(mockLeague);
    expect(presented.config.activeSeason).toBeUndefined();
    expect(presented.config.scoringConfig).toBeUndefined();
    expect(presented.config.game).toBeUndefined();
  });

  it('should return repository error when repository throws and not call presenter', async () => {
    const input: GetLeagueFullConfigInput = { leagueId: 'league-1' };

    const thrownError = new Error('Repository failure');
    leagueRepository.findById.mockRejectedValue(thrownError);

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueFullConfigErrorCode,
      { message: string }
    >;

    expect(error.code).toBe('REPOSITORY_ERROR');
    expect(error.details.message).toBe('Repository failure');
    expect(output.present).not.toHaveBeenCalled();
  });
});
