import type { Logger } from '@core/shared/domain/Logger';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { League } from '../../domain/entities/League';
import { LeagueWallet } from '../../domain/entities/league-wallet/LeagueWallet';
import { Money } from '../../domain/value-objects/Money';
import {
    WithdrawFromLeagueWalletUseCase,
    type WithdrawFromLeagueWalletErrorCode,
    type WithdrawFromLeagueWalletInput
} from './WithdrawFromLeagueWalletUseCase';

import { Transaction } from 'electron';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { LeagueWalletRepository } from '../../domain/repositories/LeagueWalletRepository';
import { TransactionRepository } from '../../domain/repositories/TransactionRepository';

describe('WithdrawFromLeagueWalletUseCase', () => {
  let leagueRepository: { findById: Mock };
  let walletRepository: { findByLeagueId: Mock; update: Mock };
  let transactionRepository: { create: Mock };
  let logger: {
    debug: Mock;
    info: Mock;
    warn: Mock;
    error: Mock;
  };
  let useCase: WithdrawFromLeagueWalletUseCase;

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

    logger = { 
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn() 
    };

    useCase = new WithdrawFromLeagueWalletUseCase(
      leagueRepository as unknown as LeagueRepository,
      walletRepository as unknown as LeagueWalletRepository,
      transactionRepository as unknown as TransactionRepository,
      logger as unknown as Logger
    );
  });

  it('returns LEAGUE_NOT_FOUND when league is missing', async () => {
    leagueRepository.findById.mockResolvedValue(null);

    const input: WithdrawFromLeagueWalletInput = {
      leagueId: 'league-1',
      requestedById: 'owner-1',
      amount: 100,
      currency: 'USD',
    };

    const result = await useCase.execute(input);

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

    expect(err.code).toBe('LEAGUE_NOT_FOUND');
    expect(err.details.message).toBe('League with id league-1 not found');
  });

  it('returns WALLET_NOT_FOUND when wallet is missing', async () => {
    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'desc',
      ownerId: 'owner-1',
    });

    leagueRepository.findById.mockResolvedValue(league);
    walletRepository.findByLeagueId.mockResolvedValue(null);

    const input: WithdrawFromLeagueWalletInput = {
      leagueId: 'league-1',
      requestedById: 'owner-1',
      amount: 100,
      currency: 'USD',
    };

    const result = await useCase.execute(input);

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

    expect(err.code).toBe('WALLET_NOT_FOUND');
    expect(err.details.message).toBe('Wallet for league league-1 not found');
  });

  it('returns UNAUTHORIZED_WITHDRAWAL when requester is not owner', async () => {
    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'desc',
      ownerId: 'owner-1',
    });

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

    leagueRepository.findById.mockResolvedValue(league);
    walletRepository.findByLeagueId.mockResolvedValue(wallet);

    const input: WithdrawFromLeagueWalletInput = {
      leagueId: 'league-1',
      requestedById: 'not-owner',
      amount: 100,
      currency: 'USD',
    };

    const result = await useCase.execute(input);

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

    expect(err.code).toBe('UNAUTHORIZED_WITHDRAWAL');
    expect(err.details.message).toBe('Only the league owner can withdraw from the league wallet');
  });

  it('returns INSUFFICIENT_FUNDS when wallet cannot withdraw amount', async () => {
    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'desc',
      ownerId: 'owner-1',
    });

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

    leagueRepository.findById.mockResolvedValue(league);
    walletRepository.findByLeagueId.mockResolvedValue(wallet);

    const input: WithdrawFromLeagueWalletInput = {
      leagueId: 'league-1',
      requestedById: 'owner-1',
      amount: 200,
      currency: 'USD',
    };

    const result = await useCase.execute(input);

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

    expect(err.code).toBe('INSUFFICIENT_FUNDS');
    expect(err.details.message).toBe('Insufficient balance for withdrawal');
  });

  it('creates withdrawal transaction and updates wallet on success', async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));

    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'desc',
      ownerId: 'owner-1',
    });

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

    leagueRepository.findById.mockResolvedValue(league);
    walletRepository.findByLeagueId.mockResolvedValue(wallet);
    transactionRepository.create.mockResolvedValue(undefined);
    walletRepository.update.mockResolvedValue(undefined);

    const input: WithdrawFromLeagueWalletInput = {
      leagueId: 'league-1',
      requestedById: 'owner-1',
      amount: 250,
      currency: 'USD',
      reason: 'Payout',
    };

    const result = await useCase.execute(input);

    expect(result.isOk()).toBe(true);
    const presented = result.unwrap();

    expect(transactionRepository.create).toHaveBeenCalledTimes(1);
    const createdTx = (transactionRepository.create as Mock).mock.calls[0]![0] as Transaction;

    const expectedTransactionId = `txn-${new Date('2025-01-01T00:00:00.000Z').getTime()}`;

    expect(createdTx.id.toString()).toBe(expectedTransactionId);
    expect(createdTx.type).toBe('withdrawal');
    expect(createdTx.amount.amount).toBe(250);
    expect(createdTx.amount.currency).toBe('USD');
    expect(createdTx.description).toBe('Payout');
    expect(createdTx.metadata).toEqual({ reason: 'Payout', requestedById: 'owner-1' });
    expect(createdTx.walletId.toString()).toBe(wallet.id.toString());

    expect(walletRepository.update).toHaveBeenCalledTimes(1);
    const updatedWallet = (walletRepository.update as Mock).mock.calls[0]![0] as LeagueWallet;

    expect(updatedWallet.balance.amount).toBe(750);
    expect(updatedWallet.balance.currency).toBe('USD');
    expect(updatedWallet.getTransactionIds()).toContain(expectedTransactionId);

    expect(presented.leagueId).toBe('league-1');
    expect(presented.amount.amount).toBe(250);
    expect(presented.amount.currency).toBe('USD');
    expect(presented.transactionId).toBe(expectedTransactionId);
    expect(presented.walletBalanceAfter.amount).toBe(750);
    expect(presented.walletBalanceAfter.currency).toBe('USD');

    const createOrder = (transactionRepository.create as Mock).mock.invocationCallOrder[0]!;
    const updateOrder = (walletRepository.update as Mock).mock.invocationCallOrder[0]!;
    expect(createOrder).toBeLessThan(updateOrder);

    vi.useRealTimers();
  });

  it('returns REPOSITORY_ERROR and logs when repository throws', async () => {
    const league = League.create({
      id: 'league-1',
      name: 'Test League',
      description: 'desc',
      ownerId: 'owner-1',
    });

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

    leagueRepository.findById.mockResolvedValue(league);
    walletRepository.findByLeagueId.mockResolvedValue(wallet);

    transactionRepository.create.mockRejectedValue(new Error('DB down'));

    const input: WithdrawFromLeagueWalletInput = {
      leagueId: 'league-1',
      requestedById: 'owner-1',
      amount: 100,
      currency: 'USD',
    };

    const result = await useCase.execute(input);

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

    expect(err.code).toBe('REPOSITORY_ERROR');
    expect(err.details.message).toBe('DB down');
    expect(logger.error).toHaveBeenCalledTimes(1);
  });
});
