import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { SponsorRepository } from '../../domain/repositories/SponsorRepository';
import { SponsorshipPricingRepository } from '../../domain/repositories/SponsorshipPricingRepository';
import { SponsorshipRequestRepository } from '../../domain/repositories/SponsorshipRequestRepository';
import { Money } from '../../domain/value-objects/Money';
import { ApplyForSponsorshipUseCase } from './ApplyForSponsorshipUseCase';

describe('ApplyForSponsorshipUseCase', () => {
  let mockSponsorshipRequestRepo: {
    create: Mock;
    hasPendingRequest: Mock;
  };
  let mockSponsorshipPricingRepo: {
    findByEntity: Mock;
  };
  let mockSponsorRepo: {
    findById: Mock;
  };
  let mockLogger: {
    debug: Mock;
    error: Mock;
    warn: Mock;
  };

  beforeEach(() => {
    mockSponsorshipRequestRepo = {
      create: vi.fn(),
      hasPendingRequest: vi.fn(),
    };
    mockSponsorshipPricingRepo = {
      findByEntity: vi.fn(),
    };
    mockSponsorRepo = {
      findById: vi.fn(),
    };
    mockLogger = {
      debug: vi.fn(),
      error: vi.fn(),
      warn: vi.fn(),
    };
  });

  it('should return error when sponsor does not exist', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue(null);

    const result = await useCase.execute({
      sponsorId: 'nonexistent',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
    });

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('SPONSOR_NOT_FOUND');
    });

  it('should return error when sponsorship pricing is not set up', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue({ id: 'sponsor1' });
    mockSponsorshipPricingRepo.findByEntity.mockResolvedValue(null);

    const result = await useCase.execute({
      sponsorId: 'sponsor1',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
    });

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('SPONSORSHIP_PRICING_NOT_SETUP');
    });

  it('should return error when entity is not accepting applications', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue({ id: 'sponsor1' });
    mockSponsorshipPricingRepo.findByEntity.mockResolvedValue({
      acceptingApplications: false,
      isSlotAvailable: vi.fn().mockReturnValue(true),
      getPrice: vi.fn().mockReturnValue(Money.create(500)),
    });

    const result = await useCase.execute({
      sponsorId: 'sponsor1',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
    });

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('ENTITY_NOT_ACCEPTING_APPLICATIONS');
    });

  it('should return error when no slots are available', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue({ id: 'sponsor1' });
    mockSponsorshipPricingRepo.findByEntity.mockResolvedValue({
      acceptingApplications: true,
      isSlotAvailable: vi.fn().mockReturnValue(false),
      getPrice: vi.fn().mockReturnValue(Money.create(500)),
    });

    const result = await useCase.execute({
      sponsorId: 'sponsor1',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
    });

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('NO_SLOTS_AVAILABLE');
    });

  it('should return error when sponsor has pending request', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue({ id: 'sponsor1' });
    mockSponsorshipPricingRepo.findByEntity.mockResolvedValue({
      acceptingApplications: true,
      isSlotAvailable: vi.fn().mockReturnValue(true),
      getPrice: vi.fn().mockReturnValue(Money.create(500)),
    });
    mockSponsorshipRequestRepo.hasPendingRequest.mockResolvedValue(true);

    const result = await useCase.execute({
      sponsorId: 'sponsor1',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
    });

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('PENDING_REQUEST_EXISTS');
    });

  it('should return error when offered amount is less than minimum', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue({ id: 'sponsor1' });
    mockSponsorshipPricingRepo.findByEntity.mockResolvedValue({
      acceptingApplications: true,
      isSlotAvailable: vi.fn().mockReturnValue(true),
      getPrice: vi.fn().mockReturnValue(Money.create(1500)),
    });
    mockSponsorshipRequestRepo.hasPendingRequest.mockResolvedValue(false);

    const result = await useCase.execute({
      sponsorId: 'sponsor1',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
    });

    expect(result.isOk()).toBe(false);
    expect(result.error!.code).toBe('OFFERED_AMOUNT_TOO_LOW');
  });

  it('should create sponsorship request and return result on success', async () => {

    const useCase = new ApplyForSponsorshipUseCase(mockSponsorshipRequestRepo as unknown as SponsorshipRequestRepository,
      mockSponsorshipPricingRepo as unknown as SponsorshipPricingRepository,
      mockSponsorRepo as unknown as SponsorRepository,
      mockLogger as unknown as Logger);

    mockSponsorRepo.findById.mockResolvedValue({ id: 'sponsor1' });
    mockSponsorshipPricingRepo.findByEntity.mockResolvedValue({
      acceptingApplications: true,
      isSlotAvailable: vi.fn().mockReturnValue(true),
      getPrice: vi.fn().mockReturnValue(Money.create(500)),
    });
    mockSponsorshipRequestRepo.hasPendingRequest.mockResolvedValue(false);
    mockSponsorshipRequestRepo.create.mockResolvedValue(undefined);

    const result = await useCase.execute({
      sponsorId: 'sponsor1',
      entityType: 'season',
      entityId: 'season1',
      tier: 'main',
      offeredAmount: 1000,
      message: 'Test message',
    });

    expect(result.isOk()).toBe(true);
    const unwrapped = result.unwrap();
    expect(unwrapped).toEqual({
      requestId: expect.any(String),
      status: 'pending',
      createdAt: expect.any(Date),
    });

    expect(mockSponsorshipRequestRepo.create).toHaveBeenCalledTimes(1);
    const createdRequest = (mockSponsorshipRequestRepo.create as Mock).mock.calls[0]?.[0] as unknown as {
      sponsorId: string;
      entityType: string;
      entityId: string;
      tier: string;
      offeredAmount: { amount: number };
      message?: string;
    };
    expect(createdRequest.sponsorId).toBe('sponsor1');
    expect(createdRequest.entityType).toBe('season');
    expect(createdRequest.entityId).toBe('season1');
    expect(createdRequest.tier).toBe('main');
    expect(createdRequest.offeredAmount.amount).toBe(1000);
    expect(createdRequest.message).toBe('Test message');
  });
});