/**
 * Integration Test: Sponsor Billing Use Case Orchestration
 *
 * Tests the orchestration logic of sponsor billing-related Use Cases:
 * - GetSponsorBillingUseCase: Retrieves sponsor billing information
 * - Validates that Use Cases correctly interact with their Ports (Repositories)
 * - Uses In-Memory adapters for fast, deterministic testing
 *
 * Focus: Business logic orchestration, NOT UI rendering
 */

import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemorySponsorRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySponsorRepository';
import { InMemorySeasonSponsorshipRepository } from '../../../adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository';
import { InMemoryPaymentRepository } from '../../../adapters/payments/persistence/inmemory/InMemoryPaymentRepository';
import { GetSponsorBillingUseCase } from '../../../core/payments/application/use-cases/GetSponsorBillingUseCase';
import { Sponsor } from '../../../core/racing/domain/entities/sponsor/Sponsor';
import { SeasonSponsorship } from '../../../core/racing/domain/entities/season/SeasonSponsorship';
import { Payment, PaymentType, PaymentStatus } from '../../../core/payments/domain/entities/Payment';
import { Money } from '../../../core/racing/domain/value-objects/Money';
import { Logger } from '../../../core/shared/domain/Logger';

describe('Sponsor Billing Use Case Orchestration', () => {
  let sponsorRepository: InMemorySponsorRepository;
  let seasonSponsorshipRepository: InMemorySeasonSponsorshipRepository;
  let paymentRepository: InMemoryPaymentRepository;
  let getSponsorBillingUseCase: GetSponsorBillingUseCase;
  let mockLogger: Logger;

  beforeAll(() => {
    mockLogger = {
      info: () => {},
      debug: () => {},
      warn: () => {},
      error: () => {},
    } as unknown as Logger;
    
    sponsorRepository = new InMemorySponsorRepository(mockLogger);
    seasonSponsorshipRepository = new InMemorySeasonSponsorshipRepository(mockLogger);
    paymentRepository = new InMemoryPaymentRepository(mockLogger);
    
    getSponsorBillingUseCase = new GetSponsorBillingUseCase(
      paymentRepository,
      seasonSponsorshipRepository,
    );
  });

  beforeEach(() => {
    sponsorRepository.clear();
    seasonSponsorshipRepository.clear();
    paymentRepository.clear();
  });

  describe('GetSponsorBillingUseCase - Success Path', () => {
    it('should retrieve billing statistics for a sponsor with paid invoices', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 2 active sponsorships
      const sponsorship1 = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship1);

      const sponsorship2 = SeasonSponsorship.create({
        id: 'sponsorship-2',
        sponsorId: 'sponsor-123',
        seasonId: 'season-2',
        tier: 'secondary',
        pricing: Money.create(500, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship2);

      // And: The sponsor has 3 paid invoices
      const payment1: Payment = {
        id: 'payment-1',
        type: PaymentType.SPONSORSHIP,
        amount: 1000,
        platformFee: 100,
        netAmount: 900,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-1',
        seasonId: 'season-1',
        status: PaymentStatus.COMPLETED,
        createdAt: new Date('2025-01-15'),
        completedAt: new Date('2025-01-15'),
      };
      await paymentRepository.create(payment1);

      const payment2: Payment = {
        id: 'payment-2',
        type: PaymentType.SPONSORSHIP,
        amount: 2000,
        platformFee: 200,
        netAmount: 1800,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-2',
        seasonId: 'season-2',
        status: PaymentStatus.COMPLETED,
        createdAt: new Date('2025-02-15'),
        completedAt: new Date('2025-02-15'),
      };
      await paymentRepository.create(payment2);

      const payment3: Payment = {
        id: 'payment-3',
        type: PaymentType.SPONSORSHIP,
        amount: 3000,
        platformFee: 300,
        netAmount: 2700,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-3',
        seasonId: 'season-3',
        status: PaymentStatus.COMPLETED,
        createdAt: new Date('2025-03-15'),
        completedAt: new Date('2025-03-15'),
      };
      await paymentRepository.create(payment3);

      // When: GetSponsorBillingUseCase.execute() is called with sponsor ID
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The result should contain billing data
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // And: The invoices should contain all 3 paid invoices
      expect(billing.invoices).toHaveLength(3);
      expect(billing.invoices[0].status).toBe('paid');
      expect(billing.invoices[1].status).toBe('paid');
      expect(billing.invoices[2].status).toBe('paid');

      // And: The stats should show correct total spent
      // Total spent = 1000 + 2000 + 3000 = 6000
      expect(billing.stats.totalSpent).toBe(6000);

      // And: The stats should show no pending payments
      expect(billing.stats.pendingAmount).toBe(0);

      // And: The stats should show no next payment date
      expect(billing.stats.nextPaymentDate).toBeNull();
      expect(billing.stats.nextPaymentAmount).toBeNull();

      // And: The stats should show correct active sponsorships
      expect(billing.stats.activeSponsorships).toBe(2);

      // And: The stats should show correct average monthly spend
      // Average monthly spend = total / months = 6000 / 3 = 2000
      expect(billing.stats.averageMonthlySpend).toBe(2000);
    });

    it('should retrieve billing statistics with pending invoices', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 1 active sponsorship
      const sponsorship = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship);

      // And: The sponsor has 1 paid invoice and 1 pending invoice
      const payment1: Payment = {
        id: 'payment-1',
        type: PaymentType.SPONSORSHIP,
        amount: 1000,
        platformFee: 100,
        netAmount: 900,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-1',
        seasonId: 'season-1',
        status: PaymentStatus.COMPLETED,
        createdAt: new Date('2025-01-15'),
        completedAt: new Date('2025-01-15'),
      };
      await paymentRepository.create(payment1);

      const payment2: Payment = {
        id: 'payment-2',
        type: PaymentType.SPONSORSHIP,
        amount: 500,
        platformFee: 50,
        netAmount: 450,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-2',
        seasonId: 'season-2',
        status: PaymentStatus.PENDING,
        createdAt: new Date('2025-02-15'),
      };
      await paymentRepository.create(payment2);

      // When: GetSponsorBillingUseCase.execute() is called with sponsor ID
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The result should contain billing data
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // And: The invoices should contain both invoices
      expect(billing.invoices).toHaveLength(2);

      // And: The stats should show correct total spent (only paid invoices)
      expect(billing.stats.totalSpent).toBe(1000);

      // And: The stats should show correct pending amount
      expect(billing.stats.pendingAmount).toBe(550); // 500 + 50

      // And: The stats should show next payment date
      expect(billing.stats.nextPaymentDate).toBeDefined();
      expect(billing.stats.nextPaymentAmount).toBe(550);

      // And: The stats should show correct active sponsorships
      expect(billing.stats.activeSponsorships).toBe(1);
    });

    it('should retrieve billing statistics with zero values when no invoices exist', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 1 active sponsorship
      const sponsorship = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship);

      // And: The sponsor has no invoices
      // When: GetSponsorBillingUseCase.execute() is called with sponsor ID
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The result should contain billing data
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // And: The invoices should be empty
      expect(billing.invoices).toHaveLength(0);

      // And: The stats should show zero values
      expect(billing.stats.totalSpent).toBe(0);
      expect(billing.stats.pendingAmount).toBe(0);
      expect(billing.stats.nextPaymentDate).toBeNull();
      expect(billing.stats.nextPaymentAmount).toBeNull();
      expect(billing.stats.activeSponsorships).toBe(1);
      expect(billing.stats.averageMonthlySpend).toBe(0);
    });

    it('should retrieve billing statistics with mixed invoice statuses', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 1 active sponsorship
      const sponsorship = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship);

      // And: The sponsor has invoices with different statuses
      const payment1: Payment = {
        id: 'payment-1',
        type: PaymentType.SPONSORSHIP,
        amount: 1000,
        platformFee: 100,
        netAmount: 900,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-1',
        seasonId: 'season-1',
        status: PaymentStatus.COMPLETED,
        createdAt: new Date('2025-01-15'),
        completedAt: new Date('2025-01-15'),
      };
      await paymentRepository.create(payment1);

      const payment2: Payment = {
        id: 'payment-2',
        type: PaymentType.SPONSORSHIP,
        amount: 500,
        platformFee: 50,
        netAmount: 450,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-2',
        seasonId: 'season-2',
        status: PaymentStatus.PENDING,
        createdAt: new Date('2025-02-15'),
      };
      await paymentRepository.create(payment2);

      const payment3: Payment = {
        id: 'payment-3',
        type: PaymentType.SPONSORSHIP,
        amount: 300,
        platformFee: 30,
        netAmount: 270,
        payerId: 'sponsor-123',
        payerType: 'sponsor',
        leagueId: 'league-3',
        seasonId: 'season-3',
        status: PaymentStatus.FAILED,
        createdAt: new Date('2025-03-15'),
      };
      await paymentRepository.create(payment3);

      // When: GetSponsorBillingUseCase.execute() is called with sponsor ID
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The result should contain billing data
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // And: The invoices should contain all 3 invoices
      expect(billing.invoices).toHaveLength(3);

      // And: The stats should show correct total spent (only paid invoices)
      expect(billing.stats.totalSpent).toBe(1000);

      // And: The stats should show correct pending amount (pending + failed)
      expect(billing.stats.pendingAmount).toBe(550); // 500 + 50

      // And: The stats should show correct active sponsorships
      expect(billing.stats.activeSponsorships).toBe(1);
    });
  });

  describe('GetSponsorBillingUseCase - Error Handling', () => {
    it('should return error when sponsor does not exist', async () => {
      // Given: No sponsor exists with the given ID
      // When: GetSponsorBillingUseCase.execute() is called with non-existent sponsor ID
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'non-existent-sponsor' });

      // Then: Should return an error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('SPONSOR_NOT_FOUND');
    });
  });

  describe('Sponsor Billing Data Orchestration', () => {
    it('should correctly aggregate billing statistics across multiple invoices', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 1 active sponsorship
      const sponsorship = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship);

      // And: The sponsor has 5 invoices with different amounts and statuses
      const invoices = [
        { id: 'payment-1', amount: 1000, status: PaymentStatus.COMPLETED, date: new Date('2025-01-15') },
        { id: 'payment-2', amount: 2000, status: PaymentStatus.COMPLETED, date: new Date('2025-02-15') },
        { id: 'payment-3', amount: 1500, status: PaymentStatus.PENDING, date: new Date('2025-03-15') },
        { id: 'payment-4', amount: 3000, status: PaymentStatus.COMPLETED, date: new Date('2025-04-15') },
        { id: 'payment-5', amount: 500, status: PaymentStatus.FAILED, date: new Date('2025-05-15') },
      ];

      for (const invoice of invoices) {
        const payment: Payment = {
          id: invoice.id,
          type: PaymentType.SPONSORSHIP,
          amount: invoice.amount,
          platformFee: invoice.amount * 0.1,
          netAmount: invoice.amount * 0.9,
          payerId: 'sponsor-123',
          payerType: 'sponsor',
          leagueId: 'league-1',
          seasonId: 'season-1',
          status: invoice.status,
          createdAt: invoice.date,
          completedAt: invoice.status === PaymentStatus.COMPLETED ? invoice.date : undefined,
        };
        await paymentRepository.create(payment);
      }

      // When: GetSponsorBillingUseCase.execute() is called
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The billing statistics should be correctly aggregated
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // Total spent = 1000 + 2000 + 3000 = 6000
      expect(billing.stats.totalSpent).toBe(6000);

      // Pending amount = 1500 + 500 = 2000
      expect(billing.stats.pendingAmount).toBe(2000);

      // Average monthly spend = 6000 / 5 = 1200
      expect(billing.stats.averageMonthlySpend).toBe(1200);

      // Active sponsorships = 1
      expect(billing.stats.activeSponsorships).toBe(1);
    });

    it('should correctly calculate average monthly spend over time', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 1 active sponsorship
      const sponsorship = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship);

      // And: The sponsor has invoices spanning 6 months
      const invoices = [
        { id: 'payment-1', amount: 1000, date: new Date('2025-01-15') },
        { id: 'payment-2', amount: 1500, date: new Date('2025-02-15') },
        { id: 'payment-3', amount: 2000, date: new Date('2025-03-15') },
        { id: 'payment-4', amount: 2500, date: new Date('2025-04-15') },
        { id: 'payment-5', amount: 3000, date: new Date('2025-05-15') },
        { id: 'payment-6', amount: 3500, date: new Date('2025-06-15') },
      ];

      for (const invoice of invoices) {
        const payment: Payment = {
          id: invoice.id,
          type: PaymentType.SPONSORSHIP,
          amount: invoice.amount,
          platformFee: invoice.amount * 0.1,
          netAmount: invoice.amount * 0.9,
          payerId: 'sponsor-123',
          payerType: 'sponsor',
          leagueId: 'league-1',
          seasonId: 'season-1',
          status: PaymentStatus.COMPLETED,
          createdAt: invoice.date,
          completedAt: invoice.date,
        };
        await paymentRepository.create(payment);
      }

      // When: GetSponsorBillingUseCase.execute() is called
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The average monthly spend should be calculated correctly
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // Total = 1000 + 1500 + 2000 + 2500 + 3000 + 3500 = 13500
      // Months = 6 (Jan to Jun)
      // Average = 13500 / 6 = 2250
      expect(billing.stats.averageMonthlySpend).toBe(2250);
    });

    it('should correctly identify next payment date from pending invoices', async () => {
      // Given: A sponsor exists with ID "sponsor-123"
      const sponsor = Sponsor.create({
        id: 'sponsor-123',
        name: 'Test Company',
        contactEmail: 'test@example.com',
      });
      await sponsorRepository.create(sponsor);

      // And: The sponsor has 1 active sponsorship
      const sponsorship = SeasonSponsorship.create({
        id: 'sponsorship-1',
        sponsorId: 'sponsor-123',
        seasonId: 'season-1',
        tier: 'main',
        pricing: Money.create(1000, 'USD'),
        status: 'active',
      });
      await seasonSponsorshipRepository.create(sponsorship);

      // And: The sponsor has multiple pending invoices with different due dates
      const invoices = [
        { id: 'payment-1', amount: 500, date: new Date('2025-03-15') },
        { id: 'payment-2', amount: 1000, date: new Date('2025-02-15') },
        { id: 'payment-3', amount: 750, date: new Date('2025-01-15') },
      ];

      for (const invoice of invoices) {
        const payment: Payment = {
          id: invoice.id,
          type: PaymentType.SPONSORSHIP,
          amount: invoice.amount,
          platformFee: invoice.amount * 0.1,
          netAmount: invoice.amount * 0.9,
          payerId: 'sponsor-123',
          payerType: 'sponsor',
          leagueId: 'league-1',
          seasonId: 'season-1',
          status: PaymentStatus.PENDING,
          createdAt: invoice.date,
        };
        await paymentRepository.create(payment);
      }

      // When: GetSponsorBillingUseCase.execute() is called
      const result = await getSponsorBillingUseCase.execute({ sponsorId: 'sponsor-123' });

      // Then: The next payment should be the earliest pending invoice
      expect(result.isOk()).toBe(true);
      const billing = result.unwrap();

      // Next payment should be from payment-3 (earliest date)
      expect(billing.stats.nextPaymentDate).toBe('2025-01-15T00:00:00.000Z');
      expect(billing.stats.nextPaymentAmount).toBe(825); // 750 + 75
    });
  });
});
