import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import {
  GetSeasonSponsorshipsUseCase,
  type GetSeasonSponsorshipsInput,
  type GetSeasonSponsorshipsResult,
  type GetSeasonSponsorshipsErrorCode,
} from './GetSeasonSponsorshipsUseCase';
import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import { Season } from '../../domain/entities/season/Season';
import { League } from '../../domain/entities/League';
import { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship';
import { Money } from '../../domain/value-objects/Money';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

describe('GetSeasonSponsorshipsUseCase', () => {
  let seasonSponsorshipRepository: {
    findBySeasonId: Mock;
  };
  let seasonRepository: {
    findById: Mock;
  };
  let leagueRepository: {
    findById: Mock;
  };
  let leagueMembershipRepository: {
    getLeagueMembers: Mock;
  };
  let raceRepository: {
    findByLeagueId: Mock;
  };

  let output: UseCaseOutputPort<GetSeasonSponsorshipsResult> & { present: Mock };

  let useCase: GetSeasonSponsorshipsUseCase;

  beforeEach(() => {
    seasonSponsorshipRepository = {
      findBySeasonId: vi.fn(),
    };
    seasonRepository = {
      findById: vi.fn(),
    };
    leagueRepository = {
      findById: vi.fn(),
    };
    leagueMembershipRepository = {
      getLeagueMembers: vi.fn(),
    };
    raceRepository = {
      findByLeagueId: vi.fn(),
    };

    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<GetSeasonSponsorshipsResult> & { present: Mock };

    useCase = new GetSeasonSponsorshipsUseCase(
      seasonSponsorshipRepository as unknown as ISeasonSponsorshipRepository,
      seasonRepository as unknown as ISeasonRepository,
      leagueRepository as unknown as ILeagueRepository,
      leagueMembershipRepository as unknown as ILeagueMembershipRepository,
      raceRepository as unknown as IRaceRepository,
      output,
    );
  });

  it('returns SEASON_NOT_FOUND when season does not exist', async () => {
    const input: GetSeasonSponsorshipsInput = { seasonId: 'season-1' };

    seasonRepository.findById.mockResolvedValue(null);

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetSeasonSponsorshipsErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('SEASON_NOT_FOUND');
    expect(err.details.message).toBe('Season not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('returns LEAGUE_NOT_FOUND when league for season does not exist', async () => {
    const input: GetSeasonSponsorshipsInput = { seasonId: 'season-1' };

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

    seasonRepository.findById.mockResolvedValue(season);
    leagueRepository.findById.mockResolvedValue(null);

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetSeasonSponsorshipsErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('LEAGUE_NOT_FOUND');
    expect(err.details.message).toBe('League not found for season');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('presents sponsorship details with computed metrics', async () => {
    const input: GetSeasonSponsorshipsInput = { seasonId: 'season-1' };

    const season = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'game-1',
      name: 'Season 1',
      status: 'active',
      startDate: new Date('2025-01-01T00:00:00.000Z'),
      endDate: new Date('2025-02-01T00:00:00.000Z'),
    });

    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'Test',
      ownerId: 'owner-1',
    });

    const sponsorship = SeasonSponsorship.create({
      id: 'sponsorship-1',
      sponsorId: 'sponsor-1',
      seasonId: 'season-1',
      tier: 'main',
      pricing: Money.create(1000, 'USD'),
      activatedAt: new Date('2025-01-02T00:00:00.000Z'),
      createdAt: new Date('2025-01-01T00:00:00.000Z'),
    });

    seasonRepository.findById.mockResolvedValue(season);
    leagueRepository.findById.mockResolvedValue(league);
    seasonSponsorshipRepository.findBySeasonId.mockResolvedValue([sponsorship]);

    leagueMembershipRepository.getLeagueMembers.mockResolvedValue([
      { driverId: 'driver-1' },
      { driverId: 'driver-2' },
      { driverId: 'driver-3' },
    ]);

    raceRepository.findByLeagueId.mockResolvedValue([
      { id: 'race-1', status: { isCompleted: () => true } },
      { id: 'race-2', status: { isCompleted: () => true } },
      { id: 'race-3', status: { isCompleted: () => false } },
    ]);

    const result = await useCase.execute(input);

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

    expect(output.present).toHaveBeenCalledTimes(1);
    const presented = (output.present as Mock).mock.calls[0]?.[0] as GetSeasonSponsorshipsResult;

    expect(presented.seasonId).toBe('season-1');
    expect(presented.sponsorships).toHaveLength(1);

    const detail = presented.sponsorships[0]!;
    expect(detail.id).toBe('sponsorship-1');
    expect(detail.leagueId).toBe('league-1');
    expect(detail.leagueName).toBe('Test League');
    expect(detail.seasonId).toBe('season-1');
    expect(detail.seasonName).toBe('Season 1');
    expect(detail.seasonStartDate).toEqual(new Date('2025-01-01T00:00:00.000Z'));
    expect(detail.seasonEndDate).toEqual(new Date('2025-02-01T00:00:00.000Z'));
    expect(detail.activatedAt).toEqual(new Date('2025-01-02T00:00:00.000Z'));

    expect(detail.metrics.drivers).toBe(3);
    expect(detail.metrics.races).toBe(3);
    expect(detail.metrics.completedRaces).toBe(2);
    expect(detail.metrics.impressions).toBe(2 * 3 * 100);

    expect(detail.pricing).toEqual({ amount: 1000, currency: 'USD' });
    expect(detail.platformFee).toEqual({ amount: 100, currency: 'USD' });
    expect(detail.netAmount).toEqual({ amount: 900, currency: 'USD' });
  });

  it('returns REPOSITORY_ERROR when repository throws', async () => {
    const input: GetSeasonSponsorshipsInput = { seasonId: 'season-1' };

    seasonRepository.findById.mockRejectedValue(new Error('DB error'));

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetSeasonSponsorshipsErrorCode,
      { message: string }
    >;
    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('DB error');
    expect(output.present).not.toHaveBeenCalled();
  });
});