import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
import {
  GetLeagueWalletUseCase,
  type GetLeagueWalletResult,
  type GetLeagueWalletInput,
  type GetLeagueWalletErrorCode,
} from './GetLeagueWalletUseCase';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository';
import type { ITransactionRepository } from '../../domain/repositories/ITransactionRepository';
import { LeagueWallet } from '../../domain/entities/league-wallet/LeagueWallet';
import { Money } from '../../domain/value-objects/Money';
import { Transaction } from '../../domain/entities/league-wallet/Transaction';
import { TransactionId } from '../../domain/entities/league-wallet/TransactionId';
import { LeagueWalletId } from '../../domain/entities/league-wallet/LeagueWalletId';
import type { UseCaseOutputPort } from '@core/shared/application';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

describe('GetLeagueWalletUseCase', () => {
  let leagueRepository: {
    exists: Mock;
  };
  let leagueWalletRepository: {
    findByLeagueId: Mock;
  };
  let transactionRepository: {
    findByWalletId: Mock;
  };
  let output: UseCaseOutputPort<GetLeagueWalletResult> & { present: Mock };
  let useCase: GetLeagueWalletUseCase;

  beforeEach(() => {
    leagueRepository = {
      exists: vi.fn(),
    };

    leagueWalletRepository = {
      findByLeagueId: vi.fn(),
    };

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

    output = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<GetLeagueWalletResult> & { present: Mock };

    useCase = new GetLeagueWalletUseCase(
      leagueRepository as unknown as ILeagueRepository,
      leagueWalletRepository as unknown as ILeagueWalletRepository,
      transactionRepository as unknown as ITransactionRepository,
      output,
    );
  });

  it('returns mapped wallet data when wallet exists', async () => {
    const leagueId = 'league-1';

    const balance = Money.create(2450, 'USD');
    const wallet = LeagueWallet.create({
      id: 'wallet-1',
      leagueId,
      balance,
    });

    leagueRepository.exists.mockResolvedValue(true);
    leagueWalletRepository.findByLeagueId.mockResolvedValue(wallet);

    const sponsorshipTx = Transaction.create({
      id: TransactionId.create('txn-1'),
      walletId: LeagueWalletId.create(wallet.id.toString()),
      type: 'sponsorship_payment',
      amount: Money.create(1200, 'USD'),
      description: 'Main Sponsor - TechCorp',
      metadata: {},
      completedAt: undefined,
    }).complete();

    const membershipTx = Transaction.create({
      id: TransactionId.create('txn-2'),
      walletId: LeagueWalletId.create(wallet.id.toString()),
      type: 'membership_payment',
      amount: Money.create(1600, 'USD'),
      description: 'Season Fee - 32 drivers',
      metadata: {},
      completedAt: undefined,
    }).complete();

    const withdrawalTx = Transaction.create({
      id: TransactionId.create('txn-3'),
      walletId: LeagueWalletId.create(wallet.id.toString()),
      type: 'withdrawal',
      amount: Money.create(430, 'USD'),
      description: 'Bank Transfer - Season 1 Payout',
      metadata: {},
      completedAt: undefined,
    }).complete();

    const pendingPrizeTx = Transaction.create({
      id: TransactionId.create('txn-4'),
      walletId: LeagueWalletId.create(wallet.id.toString()),
      type: 'prize_payout',
      amount: Money.create(150, 'USD'),
      description: 'Championship Prize Pool (reserved)',
      metadata: {},
      completedAt: undefined,
    });

    const refundTx = Transaction.create({
      id: TransactionId.create('txn-5'),
      walletId: LeagueWalletId.create(wallet.id.toString()),
      type: 'refund',
      amount: Money.create(100, 'USD'),
      description: 'Refund for cancelled sponsorship',
      metadata: {},
      completedAt: undefined,
    });

    const transactions = [
      sponsorshipTx,
      membershipTx,
      withdrawalTx,
      pendingPrizeTx,
      refundTx,
    ];

    transactionRepository.findByWalletId.mockResolvedValue(transactions);

    const input: GetLeagueWalletInput = { leagueId };

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(output.present).toHaveBeenCalledTimes(1);

    const presented = (output.present as Mock).mock.calls[0]![0] as GetLeagueWalletResult;

    expect(presented.wallet).toBe(wallet);
    expect(presented.transactions).toHaveLength(transactions.length);
    expect(presented.transactions[0]!.id).toEqual(
      transactions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0]!
        .id,
    );

    const { aggregates } = presented;

    const expectedTotalRevenue =
      sponsorshipTx.amount.add(membershipTx.amount).add(pendingPrizeTx.amount);

    const expectedTotalFees =
      sponsorshipTx.platformFee
        .add(membershipTx.platformFee)
        .add(pendingPrizeTx.platformFee);

    const expectedTotalWithdrawals = withdrawalTx.netAmount;
    const expectedPendingPayouts = pendingPrizeTx.netAmount;

    expect(aggregates.balance).toBe(balance);
    expect(aggregates.totalRevenue.amount).toBe(expectedTotalRevenue.amount);
    expect(aggregates.totalFees.amount).toBe(expectedTotalFees.amount);
    expect(aggregates.totalWithdrawals.amount).toBe(
      expectedTotalWithdrawals.amount,
    );
    expect(aggregates.pendingPayouts.amount).toBe(expectedPendingPayouts.amount);
  });

  it('returns error result when wallet is missing', async () => {
    const leagueId = 'league-missing';

    leagueRepository.exists.mockResolvedValue(true);
    leagueWalletRepository.findByLeagueId.mockResolvedValue(null);

    const input: GetLeagueWalletInput = { leagueId };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueWalletErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('WALLET_NOT_FOUND');
    expect(err.details.message).toBe('League wallet not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('returns league not found when league does not exist', async () => {
    const leagueId = 'league-missing';

    leagueRepository.exists.mockResolvedValue(false);

    const input: GetLeagueWalletInput = { leagueId };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueWalletErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('LEAGUE_NOT_FOUND');
    expect(err.details.message).toBe('League not found');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('returns repository error when repository throws', async () => {
    const leagueId = 'league-1';

    leagueRepository.exists.mockRejectedValue(new Error('DB error'));

    const input: GetLeagueWalletInput = { leagueId };

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<
      GetLeagueWalletErrorCode,
      { message: string }
    >;

    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('DB error');
    expect(output.present).not.toHaveBeenCalled();
  });
});
