import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import {
  GetSponsorSponsorshipsUseCase,
  type GetSponsorSponsorshipsInput,
  type GetSponsorSponsorshipsResult,
  type GetSponsorSponsorshipsErrorCode,
} from './GetSponsorSponsorshipsUseCase';
import { ISponsorRepository } from '../../domain/repositories/ISponsorRepository';
import { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository';
import { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import { Sponsor } from '../../domain/entities/sponsor/Sponsor';
import { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship';
import { Season } from '../../domain/entities/season/Season';
import { League } from '../../domain/entities/League';
import { Money } from '../../domain/value-objects/Money';
import type { UseCaseOutputPort } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

describe('GetSponsorSponsorshipsUseCase', () => {
  let useCase: GetSponsorSponsorshipsUseCase;
  let sponsorRepository: {
    findById: Mock;
  };
  let seasonSponsorshipRepository: {
    findBySponsorId: Mock;
  };
  let seasonRepository: {
    findById: Mock;
  };
  let leagueRepository: {
    findById: Mock;
  };
  let leagueMembershipRepository: {
    getLeagueMembers: Mock;
  };
  let raceRepository: {
    findByLeagueId: Mock;
  };
  let output: UseCaseOutputPort<GetSponsorSponsorshipsResult> & { present: Mock };

  beforeEach(() => {
    sponsorRepository = {
      findById: vi.fn(),
    };
    seasonSponsorshipRepository = {
      findBySponsorId: 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<GetSponsorSponsorshipsResult> & { present: Mock };

    useCase = new GetSponsorSponsorshipsUseCase(
      sponsorRepository as unknown as ISponsorRepository,
      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('should present sponsor sponsorships for existing sponsor', async () => {
    const sponsorId = 'sponsor-1';
    const sponsor = Sponsor.create({
      id: sponsorId,
      name: 'Test Sponsor',
      contactEmail: 'test@example.com',
    });
    const sponsorship = SeasonSponsorship.create({
      id: 'sponsorship-1',
      sponsorId,
      seasonId: 'season-1',
      tier: 'main',
      pricing: Money.create(10000, 'USD'),
    });
    const season = Season.create({
      id: 'season-1',
      leagueId: 'league-1',
      gameId: 'game-1',
      name: 'Season 1',
      status: 'active',
    });
    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'Test',
      ownerId: 'owner-1',
    });
    const memberships = [{ driverId: 'driver-1' }];
    const races = [{ id: 'race-1', status: { isCompleted: () => true } }];

    sponsorRepository.findById.mockResolvedValue(sponsor);
    seasonSponsorshipRepository.findBySponsorId.mockResolvedValue([sponsorship]);
    seasonRepository.findById.mockResolvedValue(season);
    leagueRepository.findById.mockResolvedValue(league);
    leagueMembershipRepository.getLeagueMembers.mockResolvedValue(memberships);
    raceRepository.findByLeagueId.mockResolvedValue(races);

    const input: GetSponsorSponsorshipsInput = { sponsorId };
    const result = await useCase.execute(input);

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

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

    expect(presented.sponsor).toBe(sponsor);
    expect(presented.sponsorships).toHaveLength(1);
    const summary = presented.summary;
    expect(summary.totalSponsorships).toBe(1);
    expect(summary.activeSponsorships).toBe(0); // status default may not be 'active'
    expect(summary.totalInvestment.amount).toBe(10000);
    expect(summary.totalInvestment.currency).toBe('USD');
  });

  it('should return SPONSOR_NOT_FOUND error for non-existing sponsor', async () => {
    const sponsorId = 'sponsor-1';
    sponsorRepository.findById.mockResolvedValue(null);

    const input: GetSponsorSponsorshipsInput = { sponsorId };
    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const error = result.unwrapErr() as ApplicationErrorCode<
      GetSponsorSponsorshipsErrorCode,
      { message: string }
    >;
    expect(error.code).toBe('SPONSOR_NOT_FOUND');
    expect(error.details.message).toBe('Sponsor not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return REPOSITORY_ERROR on repository failure', async () => {
    const sponsorId = 'sponsor-1';
    sponsorRepository.findById.mockRejectedValue(new Error('DB error'));

    const input: GetSponsorSponsorshipsInput = { sponsorId };
    const result = await useCase.execute(input);

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