import { Transaction } from '../../../domain/entities/transaction';
import { NotFoundError } from '../../../domain/errors/not-found-error';
import { ITransactionRepository } from '../../../domain/repositories/transaction-repository';
import { IAddressRepository } from '../../../domain/repositories/address-repository';

const collectAddressIds = (tx: Transaction): string[] => {
  const ids = new Set<string>();
  if (tx.from) ids.add(tx.from);
  if (tx.to) ids.add(tx.to);
  for (const path of tx.data) {
    if ('from' in path && path.from) ids.add(path.from);
    if ('to' in path && path.to) ids.add(path.to);
  }
  return Array.from(ids);
};

export class GetTransactionByIdUseCase {
  constructor(
    private readonly transactionRepository: ITransactionRepository,
    private readonly addressRepository: IAddressRepository,
  ) {}

  async execute(id: string): Promise<Transaction> {
    const transaction = await this.transactionRepository.findById(id);
    if (!transaction) {
      throw new NotFoundError('Transaction', id);
    }

    const addressIds = collectAddressIds(transaction);
    const addresses = await this.addressRepository.findByIds(addressIds);
    const addressMap = new Map(addresses.map(a => [a.id, a.address]));

    const dataWithAddresses = transaction.data.map(path => ({
      ...path,
      fromAddress:
        'from' in path && path.from
          ? addressMap.get(path.from) ?? null
          : null,
      toAddress:
        'to' in path && path.to ? addressMap.get(path.to) ?? null : null,
    })) as Transaction['data'];

    return new Transaction(
      transaction.id,
      transaction.hash,
      transaction.status,
      transaction.block,
      transaction.networkId,
      transaction.timestamp,
      transaction.from,
      transaction.to,
      transaction.interactedWith,
      transaction.value,
      transaction.fee,
      transaction.gasPrice,
      transaction.gasLimit,
      transaction.method,
      dataWithAddresses,
      transaction.createdAt,
      transaction.updatedAt,
      transaction.from ? addressMap.get(transaction.from) ?? null : null,
      transaction.to ? addressMap.get(transaction.to) ?? null : null,
    );
  }
}
