import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { UpsertMembershipFeeUseCase, type UpsertMembershipFeeInput } from './UpsertMembershipFeeUseCase';
import type { IMembershipFeeRepository } from '../../domain/repositories/IMembershipFeeRepository';
import { MembershipFeeType, type MembershipFee } from '../../domain/entities/MembershipFee';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';

describe('UpsertMembershipFeeUseCase', () => {
  let membershipFeeRepository: {
    findByLeagueId: Mock;
    create: Mock;
    update: Mock;
  };

  let output: {
    present: Mock;
  };

  let useCase: UpsertMembershipFeeUseCase;

  beforeEach(() => {
    membershipFeeRepository = {
      findByLeagueId: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
    };

    output = {
      present: vi.fn(),
    };

    useCase = new UpsertMembershipFeeUseCase(
      membershipFeeRepository as unknown as IMembershipFeeRepository,
      output as unknown as UseCaseOutputPort<unknown>,
    );
  });

  it('creates a fee when none exists and presents it', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
    vi.spyOn(Math, 'random').mockReturnValue(0.123456789);

    try {
      const input: UpsertMembershipFeeInput = {
        leagueId: 'league-1',
        type: MembershipFeeType.SEASON,
        amount: 100,
      };

      membershipFeeRepository.findByLeagueId.mockResolvedValue(null);
      membershipFeeRepository.create.mockImplementation(async (fee: MembershipFee) => ({ ...fee }));

      const result = await useCase.execute(input);

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

      expect(membershipFeeRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          id: expect.stringMatching(/^fee-1735689600000-[a-z0-9]{9}$/),
          leagueId: 'league-1',
          type: MembershipFeeType.SEASON,
          amount: 100,
          enabled: true,
          createdAt: new Date('2025-01-01T00:00:00.000Z'),
          updatedAt: new Date('2025-01-01T00:00:00.000Z'),
        }),
      );

      const createdFee = (output.present.mock.calls[0]?.[0] as { fee: MembershipFee }).fee;
      expect(createdFee.enabled).toBe(true);
      expect(createdFee.amount).toBe(100);
    } finally {
      vi.useRealTimers();
    }
  });

  it('updates an existing fee and sets enabled=false when amount is 0', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2025-01-02T00:00:00.000Z'));

    try {
      const input: UpsertMembershipFeeInput = {
        leagueId: 'league-1',
        seasonId: 'season-2',
        type: MembershipFeeType.MONTHLY,
        amount: 0,
      };

      const existingFee: MembershipFee = {
        id: 'fee-1',
        leagueId: 'league-1',
        seasonId: 'season-1',
        type: MembershipFeeType.SEASON,
        amount: 100,
        enabled: true,
        createdAt: new Date('2024-01-01T00:00:00.000Z'),
        updatedAt: new Date('2024-01-01T00:00:00.000Z'),
      };

      membershipFeeRepository.findByLeagueId.mockResolvedValue(existingFee);
      membershipFeeRepository.update.mockImplementation(async (fee: MembershipFee) => ({ ...fee }));

      const result = await useCase.execute(input);

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

      expect(membershipFeeRepository.update).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 'fee-1',
          leagueId: 'league-1',
          seasonId: 'season-2',
          type: MembershipFeeType.MONTHLY,
          amount: 0,
          enabled: false,
          updatedAt: new Date('2025-01-02T00:00:00.000Z'),
        }),
      );

      const updatedFee = (output.present.mock.calls[0]?.[0] as { fee: MembershipFee }).fee;
      expect(updatedFee.enabled).toBe(false);
      expect(updatedFee.amount).toBe(0);
      expect(updatedFee.seasonId).toBe('season-2');
      expect(updatedFee.type).toBe(MembershipFeeType.MONTHLY);
    } finally {
      vi.useRealTimers();
    }
  });
});