import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SponsorshipService } from './SponsorshipService';
import type { SponsorsApiClient } from '../../api/sponsors/SponsorsApiClient';
import type { SponsorshipPricingPresenter } from '../../presenters/SponsorshipPricingPresenter';
import type { SponsorSponsorshipsPresenter } from '../../presenters/SponsorSponsorshipsPresenter';
import type {
  GetEntitySponsorshipPricingResultDto,
  SponsorSponsorshipsDto,
} from '../../dtos';
import type { SponsorshipPricingViewModel, SponsorSponsorshipsViewModel } from '../../view-models';

describe('SponsorshipService', () => {
  let service: SponsorshipService;
  let mockApiClient: SponsorsApiClient;
  let mockSponsorshipPricingPresenter: SponsorshipPricingPresenter;
  let mockSponsorSponsorshipsPresenter: SponsorSponsorshipsPresenter;

  beforeEach(() => {
    mockApiClient = {
      getPricing: vi.fn(),
      getSponsorships: vi.fn(),
    } as unknown as SponsorsApiClient;

    mockSponsorshipPricingPresenter = {
      present: vi.fn(),
    } as unknown as SponsorshipPricingPresenter;

    mockSponsorSponsorshipsPresenter = {
      present: vi.fn(),
    } as unknown as SponsorSponsorshipsPresenter;

    service = new SponsorshipService(
      mockApiClient,
      mockSponsorshipPricingPresenter,
      mockSponsorSponsorshipsPresenter
    );
  });

  describe('constructor', () => {
    it('should create instance with injected dependencies', () => {
      expect(service).toBeInstanceOf(SponsorshipService);
    });
  });

  describe('getSponsorshipPricing', () => {
    it('should fetch sponsorship pricing from API and transform via presenter', async () => {
      // Arrange
      const mockDto: GetEntitySponsorshipPricingResultDto = {
        mainSlotPrice: 10000,
        secondarySlotPrice: 5000,
        currency: 'USD',
      };

      const mockViewModel: SponsorshipPricingViewModel = {
        mainSlotPrice: 10000,
        secondarySlotPrice: 5000,
        currency: 'USD',
        formattedMainSlotPrice: 'USD 10,000',
        formattedSecondarySlotPrice: 'USD 5,000',
        priceDifference: 5000,
        formattedPriceDifference: 'USD 5,000',
        secondaryDiscountPercentage: 50,
      } as SponsorshipPricingViewModel;

      vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorshipPricingPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getSponsorshipPricing();

      // Assert
      expect(mockApiClient.getPricing).toHaveBeenCalled();
      expect(mockSponsorshipPricingPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModel);
    });

    it('should handle different currencies', async () => {
      // Arrange
      const mockDto: GetEntitySponsorshipPricingResultDto = {
        mainSlotPrice: 8000,
        secondarySlotPrice: 4000,
        currency: 'EUR',
      };

      const mockViewModel: SponsorshipPricingViewModel = {
        mainSlotPrice: 8000,
        secondarySlotPrice: 4000,
        currency: 'EUR',
        formattedMainSlotPrice: 'EUR 8,000',
        formattedSecondarySlotPrice: 'EUR 4,000',
        priceDifference: 4000,
        formattedPriceDifference: 'EUR 4,000',
        secondaryDiscountPercentage: 50,
      } as SponsorshipPricingViewModel;

      vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorshipPricingPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getSponsorshipPricing();

      // Assert
      expect(mockApiClient.getPricing).toHaveBeenCalled();
      expect(mockSponsorshipPricingPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModel);
      expect(result.currency).toBe('EUR');
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const error = new Error('Failed to fetch pricing');
      vi.mocked(mockApiClient.getPricing).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getSponsorshipPricing()).rejects.toThrow('Failed to fetch pricing');
      expect(mockApiClient.getPricing).toHaveBeenCalled();
      expect(mockSponsorshipPricingPresenter.present).not.toHaveBeenCalled();
    });

    it('should handle zero prices', async () => {
      // Arrange
      const mockDto: GetEntitySponsorshipPricingResultDto = {
        mainSlotPrice: 0,
        secondarySlotPrice: 0,
        currency: 'USD',
      };

      const mockViewModel: SponsorshipPricingViewModel = {
        mainSlotPrice: 0,
        secondarySlotPrice: 0,
        currency: 'USD',
        formattedMainSlotPrice: 'USD 0',
        formattedSecondarySlotPrice: 'USD 0',
        priceDifference: 0,
        formattedPriceDifference: 'USD 0',
        secondaryDiscountPercentage: 0,
      } as SponsorshipPricingViewModel;

      vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorshipPricingPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getSponsorshipPricing();

      // Assert
      expect(mockApiClient.getPricing).toHaveBeenCalled();
      expect(mockSponsorshipPricingPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModel);
    });
  });

  describe('getSponsorSponsorships', () => {
    it('should fetch sponsor sponsorships and transform via presenter', async () => {
      // Arrange
      const sponsorId = 'sponsor-123';

      const mockDto: SponsorSponsorshipsDto = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        sponsorships: [
          {
            id: 'sponsorship-1',
            leagueId: 'league-1',
            leagueName: 'League One',
            seasonId: 'season-1',
            tier: 'main',
            status: 'active',
            amount: 10000,
            currency: 'USD',
          },
          {
            id: 'sponsorship-2',
            leagueId: 'league-2',
            leagueName: 'League Two',
            seasonId: 'season-2',
            tier: 'secondary',
            status: 'active',
            amount: 5000,
            currency: 'USD',
          },
        ],
      };

      const mockViewModel: SponsorSponsorshipsViewModel = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        sponsorships: [],
        totalCount: 2,
        activeCount: 2,
        hasSponsorships: true,
        totalInvestment: 15000,
        formattedTotalInvestment: 'USD 15,000',
      } as SponsorSponsorshipsViewModel;

      vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorSponsorshipsPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getSponsorSponsorships(sponsorId);

      // Assert
      expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId);
      expect(mockSponsorSponsorshipsPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModel);
    });

    it('should return null when sponsorships are not found', async () => {
      // Arrange
      const sponsorId = 'non-existent';

      vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(null);

      // Act
      const result = await service.getSponsorSponsorships(sponsorId);

      // Assert
      expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId);
      expect(mockSponsorSponsorshipsPresenter.present).not.toHaveBeenCalled();
      expect(result).toBeNull();
    });

    it('should handle empty sponsorships list', async () => {
      // Arrange
      const sponsorId = 'sponsor-123';

      const mockDto: SponsorSponsorshipsDto = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        sponsorships: [],
      };

      const mockViewModel: SponsorSponsorshipsViewModel = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        sponsorships: [],
        totalCount: 0,
        activeCount: 0,
        hasSponsorships: false,
        totalInvestment: 0,
        formattedTotalInvestment: 'USD 0',
      } as SponsorSponsorshipsViewModel;

      vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorSponsorshipsPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getSponsorSponsorships(sponsorId);

      // Assert
      expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId);
      expect(mockSponsorSponsorshipsPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModel);
      expect(result?.totalCount).toBe(0);
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const sponsorId = 'sponsor-123';
      const error = new Error('Failed to fetch sponsorships');
      vi.mocked(mockApiClient.getSponsorships).mockRejectedValue(error);

      // Act & Assert
      await expect(service.getSponsorSponsorships(sponsorId)).rejects.toThrow('Failed to fetch sponsorships');
      expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId);
      expect(mockSponsorSponsorshipsPresenter.present).not.toHaveBeenCalled();
    });

    it('should handle multiple sponsorship tiers', async () => {
      // Arrange
      const sponsorId = 'sponsor-456';

      const mockDto: SponsorSponsorshipsDto = {
        sponsorId,
        sponsorName: 'Sponsor Beta',
        sponsorships: [
          {
            id: 'sponsorship-1',
            leagueId: 'league-1',
            leagueName: 'League One',
            seasonId: 'season-1',
            tier: 'main',
            status: 'active',
            amount: 10000,
            currency: 'USD',
          },
          {
            id: 'sponsorship-2',
            leagueId: 'league-2',
            leagueName: 'League Two',
            seasonId: 'season-2',
            tier: 'secondary',
            status: 'pending',
            amount: 5000,
            currency: 'USD',
          },
          {
            id: 'sponsorship-3',
            leagueId: 'league-3',
            leagueName: 'League Three',
            seasonId: 'season-3',
            tier: 'main',
            status: 'expired',
            amount: 10000,
            currency: 'USD',
          },
        ],
      };

      const mockViewModel: SponsorSponsorshipsViewModel = {
        sponsorId,
        sponsorName: 'Sponsor Beta',
        sponsorships: [],
        totalCount: 3,
        activeCount: 1,
        hasSponsorships: true,
        totalInvestment: 25000,
        formattedTotalInvestment: 'USD 25,000',
      } as SponsorSponsorshipsViewModel;

      vi.mocked(mockApiClient.getSponsorships).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorSponsorshipsPresenter.present).mockReturnValue(mockViewModel);

      // Act
      const result = await service.getSponsorSponsorships(sponsorId);

      // Assert
      expect(mockApiClient.getSponsorships).toHaveBeenCalledWith(sponsorId);
      expect(mockSponsorSponsorshipsPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModel);
      expect(result?.totalCount).toBe(3);
      expect(result?.activeCount).toBe(1);
    });
  });
});