import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository';
import type { ITransactionRepository } from '../../domain/repositories/ITransactionRepository';
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';

export type WithdrawFromLeagueWalletInput = {
  leagueId: string;
  requestedById: string;
  amount: number;
  currency: 'USD' | 'EUR' | 'GBP';
  reason?: string;
};

export type WithdrawFromLeagueWalletResult = {
  leagueId: string;
  amount: Money;
  transactionId: string;
  walletBalanceAfter: Money;
};

export type WithdrawFromLeagueWalletErrorCode =
  | 'LEAGUE_NOT_FOUND'
  | 'WALLET_NOT_FOUND'
  | 'INSUFFICIENT_FUNDS'
  | 'UNAUTHORIZED_WITHDRAWAL'
  | 'REPOSITORY_ERROR';

/**
 * Use Case for withdrawing from league wallet.
 */
export class WithdrawFromLeagueWalletUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly walletRepository: ILeagueWalletRepository,
    private readonly transactionRepository: ITransactionRepository,
    private readonly logger: Logger,
    private readonly output: UseCaseOutputPort<WithdrawFromLeagueWalletResult>,
  ) {}

  async execute(
    input: WithdrawFromLeagueWalletInput,
  ): Promise<
    Result<
      void,
      ApplicationErrorCode<
        WithdrawFromLeagueWalletErrorCode,
        {
          message: string;
        }
      >
    >
  > {
    try {
      const league = await this.leagueRepository.findById(input.leagueId);
      if (!league) {
        return Result.err({
          code: 'LEAGUE_NOT_FOUND',
          details: { message: `League with id ${input.leagueId} not found` },
        });
      }

      const wallet = await this.walletRepository.findByLeagueId(input.leagueId);
      if (!wallet) {
        return Result.err({
          code: 'WALLET_NOT_FOUND',
          details: { message: `Wallet for league ${input.leagueId} not found` },
        });
      }

      if (league.ownerId.toString() !== input.requestedById) {
        return Result.err({
          code: 'UNAUTHORIZED_WITHDRAWAL',
          details: { message: 'Only the league owner can withdraw from the league wallet' },
        });
      }

      const withdrawalAmount = Money.create(input.amount, input.currency);

      if (!wallet.canWithdraw(withdrawalAmount)) {
        return Result.err({
          code: 'INSUFFICIENT_FUNDS',
          details: { message: 'Insufficient balance for withdrawal' },
        });
      }

      const transactionId = TransactionId.create(`txn-${Date.now()}`);
      const transaction = Transaction.create({
        id: transactionId,
        walletId: LeagueWalletId.create(wallet.id.toString()),
        type: 'withdrawal',
        amount: withdrawalAmount,
        description: input.reason ?? 'League wallet withdrawal',
        metadata: {
          reason: input.reason,
          requestedById: input.requestedById,
        },
        completedAt: undefined,
      });

      const updatedWallet = wallet.withdrawFunds(withdrawalAmount, transactionId.toString());

      await this.transactionRepository.create(transaction);
      await this.walletRepository.update(updatedWallet);

      const result: WithdrawFromLeagueWalletResult = {
        leagueId: input.leagueId,
        amount: withdrawalAmount,
        transactionId: transactionId.toString(),
        walletBalanceAfter: updatedWallet.balance,
      };

      this.output.present(result);

      return Result.ok(undefined);
    } catch (error) {
      this.logger.error(
        'Failed to withdraw from league wallet',
        error instanceof Error ? error : undefined,
        {
          leagueId: input.leagueId,
          requestedById: input.requestedById,
        },
      );

      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: {
          message:
            error instanceof Error
              ? error.message
              : 'Failed to withdraw from league wallet',
        },
      });
    }
  }
}
