import type { NotificationService } from '@core/notifications/application/ports/NotificationService';
import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
import type { Logger } from '@core/shared/application';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { LeagueWallet } from '../../domain/entities/league-wallet/LeagueWallet';
import { Season } from '../../domain/entities/season/Season';
import { SponsorshipRequest } from '../../domain/entities/SponsorshipRequest';
import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository';
import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository';
import { Money } from '../../domain/value-objects/Money';
import { AcceptSponsorshipRequestUseCase } from './AcceptSponsorshipRequestUseCase';

describe('AcceptSponsorshipRequestUseCase', () => {
  let mockSponsorshipRequestRepo: {
    findById: Mock;
    update: Mock;
  };
  let mockSeasonSponsorshipRepo: {
    create: Mock;
  };
  let mockSeasonRepo: {
    findById: Mock;
  };
  let mockNotificationService: {
    sendNotification: Mock;
  };
  let processPayment: Mock;
  let mockWalletRepo: {
    findById: Mock;
    update: Mock;
  };
  let mockLeagueWalletRepo: {
    findById: Mock;
    update: Mock;
  };
  let mockLogger: {
    debug: Mock;
    info: Mock;
    warn: Mock;
    error: Mock;
  };

  beforeEach(() => {
    mockSponsorshipRequestRepo = {
      findById: vi.fn(),
      update: vi.fn(),
    };
    mockSeasonSponsorshipRepo = {
      create: vi.fn(),
    };
    mockSeasonRepo = {
      findById: vi.fn(),
    };
    mockNotificationService = {
      sendNotification: vi.fn(),
    };
    processPayment = vi.fn();
    mockWalletRepo = {
      findById: vi.fn(),
      update: vi.fn(),
    };
    mockLeagueWalletRepo = {
      findById: vi.fn(),
      update: vi.fn(),
    };
    mockLogger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
  });

  it('should send notification to sponsor, process payment, update wallets, and present result when accepting season sponsorship', async () => {
    const output = {
      present: vi.fn(),
    };

    const useCase = new AcceptSponsorshipRequestUseCase(
      mockSponsorshipRequestRepo as unknown as ISponsorshipRequestRepository,
      mockSeasonSponsorshipRepo as unknown as ISeasonSponsorshipRepository,
      mockSeasonRepo as unknown as ISeasonRepository,
      mockNotificationService as unknown as NotificationService,
      processPayment,
      mockWalletRepo as unknown as IWalletRepository,
      mockLeagueWalletRepo as unknown as ILeagueWalletRepository,
      mockLogger as unknown as Logger,
      output,
    );

    const request = SponsorshipRequest.create({
      id: 'req1',
      sponsorId: 'sponsor1',
      entityId: 'season1',
      entityType: 'season',
      tier: 'main',
      offeredAmount: Money.create(1000),
      status: 'pending',
    });

    const season = Season.create({
      id: 'season1',
      leagueId: 'league1',
      gameId: 'game1',
      name: 'Season 1',
      startDate: new Date(),
      endDate: new Date(),
    });

    mockSponsorshipRequestRepo.findById.mockResolvedValue(request);
    mockSeasonRepo.findById.mockResolvedValue(season);
    mockNotificationService.sendNotification.mockResolvedValue(undefined);
    processPayment.mockResolvedValue({
      success: true,
      transactionId: 'txn1',
      timestamp: new Date(),
    });
    mockWalletRepo.findById.mockResolvedValue({
      id: 'sponsor1',
      leagueId: 'league1',
      balance: 2000,
      totalRevenue: 0,
      totalPlatformFees: 0,
      totalWithdrawn: 0,
      currency: 'USD',
      createdAt: new Date(),
    });
    const leagueWallet = LeagueWallet.create({
      id: 'league1',
      leagueId: 'league1',
      balance: Money.create(500),
    });
    mockLeagueWalletRepo.findById.mockResolvedValue(leagueWallet);

    const result = await useCase.execute({
      requestId: 'req1',
      respondedBy: 'driver1',
    });

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

    expect(mockNotificationService.sendNotification).toHaveBeenCalledWith({
      recipientId: 'sponsor1',
      type: 'sponsorship_request_accepted',
      title: 'Sponsorship Accepted',
      body: 'Your sponsorship request for Season 1 has been accepted.',
      channel: 'in_app',
      urgency: 'toast',
      data: {
        requestId: 'req1',
        sponsorshipId: expect.any(String),
      },
    });
    expect(processPayment).toHaveBeenCalledWith({
      amount: 1000,
      payerId: 'sponsor1',
      description: 'Sponsorship payment for season season1',
      metadata: { requestId: 'req1' },
    });
    expect(mockWalletRepo.update).toHaveBeenCalledWith(
      expect.objectContaining({
        id: 'sponsor1',
        balance: 1000,
      }),
    );
    expect(mockLeagueWalletRepo.update).toHaveBeenCalledTimes(1);
    const updatedLeagueWallet = (mockLeagueWalletRepo.update as Mock).mock.calls[0]?.[0] as LeagueWallet;

    type ToStringable = { toString(): string };
    const asString = (value: unknown): string => {
      if (typeof value === 'string') return value;
      if (
        value &&
        typeof value === 'object' &&
        'toString' in value &&
        typeof (value as ToStringable).toString === 'function'
      ) {
        return (value as ToStringable).toString();
      }
      return String(value);
    };

    const updatedLeagueWalletId = (updatedLeagueWallet as unknown as { id: unknown }).id;
    const updatedLeagueWalletBalanceAmount = (updatedLeagueWallet as unknown as { balance: { amount: number } })
      .balance.amount;

    expect(asString(updatedLeagueWalletId)).toBe('league1');
    expect(updatedLeagueWalletBalanceAmount).toBe(1400);

    expect(output.present).toHaveBeenCalledWith({
      requestId: 'req1',
      sponsorshipId: expect.any(String),
      status: 'accepted',
      acceptedAt: expect.any(Date),
      platformFee: expect.any(Number),
      netAmount: expect.any(Number),
    });
  });
});