import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
  CreateLeagueWithSeasonAndScoringUseCase,
  type CreateLeagueWithSeasonAndScoringCommand,
  type CreateLeagueWithSeasonAndScoringResult,
} from './CreateLeagueWithSeasonAndScoringUseCase';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '../../domain/repositories/ILeagueScoringConfigRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';

describe('CreateLeagueWithSeasonAndScoringUseCase', () => {
  let useCase: CreateLeagueWithSeasonAndScoringUseCase;
  let leagueRepository: {
    create: Mock;
  };
  let seasonRepository: {
    create: Mock;
  };
  let leagueScoringConfigRepository: {
    save: Mock;
  };
  let getLeagueScoringPresetById: Mock;
  let logger: {
    debug: Mock;
    info: Mock;
    warn: Mock;
    error: Mock;
  };
  let output: { present: Mock } & UseCaseOutputPort<CreateLeagueWithSeasonAndScoringResult>;

  beforeEach(() => {
    leagueRepository = {
      create: vi.fn(),
    };
    seasonRepository = {
      create: vi.fn(),
    };
    leagueScoringConfigRepository = {
      save: vi.fn(),
    };
    getLeagueScoringPresetById = vi.fn();
    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    output = { present: vi.fn() } as unknown as typeof output;
    useCase = new CreateLeagueWithSeasonAndScoringUseCase(
      leagueRepository as unknown as ILeagueRepository,
      seasonRepository as unknown as ISeasonRepository,
      leagueScoringConfigRepository as unknown as ILeagueScoringConfigRepository,
      getLeagueScoringPresetById,
      logger as unknown as Logger,
      output,
    );
  });

  it('should create league, season, and scoring successfully', async () => {
    const command = {
      name: 'Test League',
      description: 'A test league',
      visibility: 'unranked' as const,
      ownerId: 'owner-1',
      gameId: 'game-1',
      maxDrivers: 20,
      maxTeams: 5,
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
      scoringPresetId: 'club-default',
    };

    const mockPreset = {
      id: 'club-default',
      name: 'Club Default',
    };

    getLeagueScoringPresetById.mockResolvedValue(mockPreset);
    leagueRepository.create.mockResolvedValue(undefined);
    seasonRepository.create.mockResolvedValue(undefined);
    leagueScoringConfigRepository.save.mockResolvedValue(undefined);

    const result = await useCase.execute(command);

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

    expect(output.present).toHaveBeenCalledTimes(1);
    const presented = (output.present as Mock).mock.calls[0]?.[0] as unknown as CreateLeagueWithSeasonAndScoringResult;
    expect(presented?.league.id.toString()).toBeDefined();
    expect(presented?.season.id).toBeDefined();
    expect(presented?.scoringConfig.seasonId.toString()).toBe(presented?.season.id);
    expect(leagueRepository.create).toHaveBeenCalledTimes(1);
    expect(seasonRepository.create).toHaveBeenCalledTimes(1);
    expect(leagueScoringConfigRepository.save).toHaveBeenCalledTimes(1);
  });

  it('should return error when league name is empty', async () => {
    const command = {
      name: '',
      description: 'Test description',
      visibility: 'unranked' as const,
      ownerId: 'owner-1',
      gameId: 'game-1',
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('VALIDATION_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('League name is required');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when ownerId is empty', async () => {
    const command = {
      name: 'Test League',
      description: 'Test description',
      visibility: 'unranked' as const,
      ownerId: '',
      gameId: 'game-1',
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const result = await useCase.execute(command as CreateLeagueWithSeasonAndScoringCommand);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('VALIDATION_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('League ownerId is required');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when gameId is empty', async () => {
    const command = {
      name: 'Test League',
      description: 'Test description',
      visibility: 'unranked' as const,
      ownerId: 'owner-1',
      gameId: '',
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('VALIDATION_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('gameId is required');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when visibility is missing', async () => {
    const command: Partial<CreateLeagueWithSeasonAndScoringCommand> = {
      name: 'Test League',
      ownerId: 'owner-1',
      gameId: 'game-1',
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const result = await useCase.execute(command as CreateLeagueWithSeasonAndScoringCommand);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('VALIDATION_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('visibility is required');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when maxDrivers is invalid', async () => {
    const command = {
      name: 'Test League',
      description: 'Test description',
      visibility: 'unranked' as const,
      ownerId: 'owner-1',
      gameId: 'game-1',
      maxDrivers: 0,
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('VALIDATION_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('maxDrivers must be greater than 0 when provided');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when ranked league has insufficient drivers', async () => {
    const command = {
      name: 'Test League',
      description: 'Test description',
      visibility: 'ranked' as const,
      ownerId: 'owner-1',
      gameId: 'game-1',
      maxDrivers: 5,
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('VALIDATION_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toContain('Ranked leagues require at least 10 drivers');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when scoring preset is unknown', async () => {
    const command = {
      name: 'Test League',
      description: 'Test description',
      visibility: 'unranked' as const,
      ownerId: 'owner-1',
      gameId: 'game-1',
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
      scoringPresetId: 'unknown-preset',
    };

    getLeagueScoringPresetById.mockResolvedValue(undefined);

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('UNKNOWN_PRESET');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('Unknown scoring preset: unknown-preset');
    }
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when repository throws', async () => {
    const command = {
      name: 'Test League',
      description: 'Test description',
      visibility: 'unranked' as const,
      ownerId: 'owner-1',
      gameId: 'game-1',
      enableDriverChampionship: true,
      enableTeamChampionship: true,
      enableNationsChampionship: false,
      enableTrophyChampionship: false,
    };

    const mockPreset = {
      id: 'club-default',
      name: 'Club Default',
    };

    getLeagueScoringPresetById.mockResolvedValue(mockPreset);
    leagueRepository.create.mockRejectedValue(new Error('DB error'));

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('REPOSITORY_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toBe('DB error');
    }
    expect(output.present).not.toHaveBeenCalled();
  });
});