import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { GetWalletUseCase, type GetWalletInput } from './GetWalletUseCase';
import type { ITransactionRepository, IWalletRepository } from '../../domain/repositories/IWalletRepository';
import type { Transaction, Wallet } from '../../domain/entities/Wallet';
import { TransactionType } from '../../domain/entities/Wallet';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';

describe('GetWalletUseCase', () => {
  let walletRepository: {
    findByLeagueId: Mock;
    create: Mock;
  };

  let transactionRepository: {
    findByWalletId: Mock;
  };

  let output: {
    present: Mock;
  };

  let useCase: GetWalletUseCase;

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

    transactionRepository = {
      findByWalletId: vi.fn(),
    };

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

    useCase = new GetWalletUseCase(
      walletRepository as unknown as IWalletRepository,
      transactionRepository as unknown as ITransactionRepository,
      output as unknown as UseCaseOutputPort<unknown>,
    );
  });

  it('returns INVALID_INPUT when leagueId is missing', async () => {
    const input = { leagueId: '' } as unknown as GetWalletInput;

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    expect(result.unwrapErr().code).toBe('INVALID_INPUT');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('presents existing wallet and transactions sorted desc by createdAt', async () => {
    const input: GetWalletInput = { leagueId: 'league-1' };

    const wallet: Wallet = {
      id: 'wallet-1',
      leagueId: 'league-1',
      balance: 50,
      totalRevenue: 100,
      totalPlatformFees: 5,
      totalWithdrawn: 10,
      currency: 'USD',
      createdAt: new Date('2025-01-01T00:00:00.000Z'),
    };

    const older: Transaction = {
      id: 'txn-older',
      walletId: 'wallet-1',
      type: TransactionType.DEPOSIT,
      amount: 25,
      description: 'Older',
      createdAt: new Date('2025-01-01T00:00:00.000Z'),
    };

    const newer: Transaction = {
      id: 'txn-newer',
      walletId: 'wallet-1',
      type: TransactionType.WITHDRAWAL,
      amount: 10,
      description: 'Newer',
      createdAt: new Date('2025-01-02T00:00:00.000Z'),
    };

    walletRepository.findByLeagueId.mockResolvedValue(wallet);
    transactionRepository.findByWalletId.mockResolvedValue([older, newer]);

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(transactionRepository.findByWalletId).toHaveBeenCalledWith('wallet-1');
    expect(output.present).toHaveBeenCalledWith({
      wallet,
      transactions: [newer, older],
    });
  });

  it('creates wallet when missing, then presents wallet and transactions', async () => {
    const input: GetWalletInput = { leagueId: 'league-1' };

    vi.useFakeTimers();
    vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
    vi.spyOn(Math, 'random').mockReturnValue(0.123456789);

    try {
      walletRepository.findByLeagueId.mockResolvedValue(null);

      walletRepository.create.mockImplementation(async (w: Wallet) => w);

      transactionRepository.findByWalletId.mockResolvedValue([]);

      const result = await useCase.execute(input);

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

      expect(walletRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({
          id: expect.stringMatching(/^wallet-1735689600000-[a-z0-9]{9}$/),
          leagueId: 'league-1',
          balance: 0,
          totalRevenue: 0,
          totalPlatformFees: 0,
          totalWithdrawn: 0,
          currency: 'USD',
          createdAt: new Date('2025-01-01T00:00:00.000Z'),
        }),
      );

      const createdWalletArg = walletRepository.create.mock.calls[0]?.[0] as Wallet;
      expect(transactionRepository.findByWalletId).toHaveBeenCalledWith(createdWalletArg.id);

      expect(output.present).toHaveBeenCalledWith({
        wallet: createdWalletArg,
        transactions: [],
      });
    } finally {
      vi.useRealTimers();
    }
  });
});