import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SponsorService } from './SponsorService';
import type { SponsorsApiClient } from '../../api/sponsors/SponsorsApiClient';
import type { SponsorListPresenter } from '../../presenters/SponsorListPresenter';
import type { SponsorDashboardPresenter } from '../../presenters/SponsorDashboardPresenter';
import type { SponsorSponsorshipsPresenter } from '../../presenters/SponsorSponsorshipsPresenter';
import type {
  GetSponsorsOutputDto,
  SponsorDashboardDto,
  SponsorSponsorshipsDto,
  CreateSponsorInputDto,
  CreateSponsorOutputDto,
  GetEntitySponsorshipPricingResultDto,
} from '../../dtos';
import type { SponsorViewModel, SponsorDashboardViewModel, SponsorSponsorshipsViewModel } from '../../view-models';

describe('SponsorService', () => {
  let service: SponsorService;
  let mockApiClient: SponsorsApiClient;
  let mockSponsorListPresenter: SponsorListPresenter;
  let mockSponsorDashboardPresenter: SponsorDashboardPresenter;
  let mockSponsorSponsorshipsPresenter: SponsorSponsorshipsPresenter;

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

    mockSponsorListPresenter = {
      present: vi.fn(),
    } as unknown as SponsorListPresenter;

    mockSponsorDashboardPresenter = {
      present: vi.fn(),
    } as unknown as SponsorDashboardPresenter;

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

    service = new SponsorService(
      mockApiClient,
      mockSponsorListPresenter,
      mockSponsorDashboardPresenter,
      mockSponsorSponsorshipsPresenter
    );
  });

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

  describe('getAllSponsors', () => {
    it('should fetch all sponsors from API and transform via presenter', async () => {
      // Arrange
      const mockDto: GetSponsorsOutputDto = {
        sponsors: [
          {
            id: 'sponsor-1',
            name: 'Sponsor Alpha',
            logoUrl: 'https://example.com/logo1.png',
            websiteUrl: 'https://alpha.com',
          },
          {
            id: 'sponsor-2',
            name: 'Sponsor Beta',
            logoUrl: 'https://example.com/logo2.png',
            websiteUrl: 'https://beta.com',
          },
        ],
      };

      const mockViewModels: SponsorViewModel[] = [
        {
          id: 'sponsor-1',
          name: 'Sponsor Alpha',
          logoUrl: 'https://example.com/logo1.png',
          websiteUrl: 'https://alpha.com',
        } as SponsorViewModel,
        {
          id: 'sponsor-2',
          name: 'Sponsor Beta',
          logoUrl: 'https://example.com/logo2.png',
          websiteUrl: 'https://beta.com',
        } as SponsorViewModel,
      ];

      vi.mocked(mockApiClient.getAll).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorListPresenter.present).mockReturnValue(mockViewModels);

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

      // Assert
      expect(mockApiClient.getAll).toHaveBeenCalled();
      expect(mockSponsorListPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual(mockViewModels);
    });

    it('should handle empty sponsors list', async () => {
      // Arrange
      const mockDto: GetSponsorsOutputDto = {
        sponsors: [],
      };

      const mockViewModels: SponsorViewModel[] = [];

      vi.mocked(mockApiClient.getAll).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorListPresenter.present).mockReturnValue(mockViewModels);

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

      // Assert
      expect(mockApiClient.getAll).toHaveBeenCalled();
      expect(mockSponsorListPresenter.present).toHaveBeenCalledWith(mockDto);
      expect(result).toEqual([]);
    });

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

      // Act & Assert
      await expect(service.getAllSponsors()).rejects.toThrow('Failed to fetch sponsors');
      expect(mockApiClient.getAll).toHaveBeenCalled();
      expect(mockSponsorListPresenter.present).not.toHaveBeenCalled();
    });
  });

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

      const mockDto: SponsorDashboardDto = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        totalSponsorships: 10,
        activeSponsorships: 7,
        totalInvestment: 50000,
      };

      const mockViewModel: SponsorDashboardViewModel = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        totalSponsorships: 10,
        activeSponsorships: 7,
        totalInvestment: 50000,
      } as SponsorDashboardViewModel;

      vi.mocked(mockApiClient.getDashboard).mockResolvedValue(mockDto);
      vi.mocked(mockSponsorDashboardPresenter.present).mockReturnValue(mockViewModel);

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

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

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

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

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

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

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

      // Act & Assert
      await expect(service.getSponsorDashboard(sponsorId)).rejects.toThrow('Failed to fetch dashboard');
      expect(mockApiClient.getDashboard).toHaveBeenCalledWith(sponsorId);
      expect(mockSponsorDashboardPresenter.present).not.toHaveBeenCalled();
    });
  });

  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',
          },
        ],
      };

      const mockViewModel: SponsorSponsorshipsViewModel = {
        sponsorId,
        sponsorName: 'Sponsor Alpha',
        sponsorships: [],
      } 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 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();
    });
  });

  describe('createSponsor', () => {
    it('should create a new sponsor', async () => {
      // Arrange
      const input: CreateSponsorInputDto = {
        name: 'New Sponsor',
        logoUrl: 'https://example.com/logo.png',
        websiteUrl: 'https://newsponsor.com',
        userId: 'user-123',
      };

      const mockOutput: CreateSponsorOutputDto = {
        sponsorId: 'sponsor-new',
        success: true,
      };

      vi.mocked(mockApiClient.create).mockResolvedValue(mockOutput);

      // Act
      const result = await service.createSponsor(input);

      // Assert
      expect(mockApiClient.create).toHaveBeenCalledWith(input);
      expect(result).toEqual(mockOutput);
    });

    it('should propagate errors from API client', async () => {
      // Arrange
      const input: CreateSponsorInputDto = {
        name: 'New Sponsor',
        userId: 'user-123',
      };
      const error = new Error('Failed to create sponsor');
      vi.mocked(mockApiClient.create).mockRejectedValue(error);

      // Act & Assert
      await expect(service.createSponsor(input)).rejects.toThrow('Failed to create sponsor');
      expect(mockApiClient.create).toHaveBeenCalledWith(input);
    });
  });

  describe('getSponsorshipPricing', () => {
    it('should fetch sponsorship pricing', async () => {
      // Arrange
      const mockPricing: GetEntitySponsorshipPricingResultDto = {
        pricingItems: [
          {
            tier: 'main',
            price: 10000,
            currency: 'USD',
            benefits: ['Logo placement', 'Race announcements'],
          },
          {
            tier: 'secondary',
            price: 5000,
            currency: 'USD',
            benefits: ['Logo placement'],
          },
        ],
      };

      vi.mocked(mockApiClient.getPricing).mockResolvedValue(mockPricing);

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

      // Assert
      expect(mockApiClient.getPricing).toHaveBeenCalled();
      expect(result).toEqual(mockPricing);
    });

    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();
    });
  });
});