<?php
namespace App\Application\Services\Blockchain;

use App\Application\Services\PaymentStateMachine;
use App\Application\Services\Webhooks\MerchantWebhookDispatcher;
use App\Support\AtomicAmount;
use App\Domain\Blockchain\Data\ChainTransaction as ChainData;
use App\Domain\Payments\PaymentStatus;
use App\Events\PaymentDetected;
use App\Jobs\RefreshTransactionFinalityJob;
use App\Models\{Asset,ChainTransaction,InvoiceTransaction,WalletAddress};
use Illuminate\Support\Facades\DB;
use DomainException;
final class Trc20TransactionProcessor
{
 public function __construct(private PaymentStateMachine $states, private MerchantWebhookDispatcher $webhooks){}
 public function process(ChainData $data,Asset $asset):ChainTransaction {
  return DB::transaction(function()use($data,$asset){
   if($data->network!==$asset->network)throw new DomainException('NETWORK_MISMATCH');
   if(strcasecmp((string)$data->contractAddress,(string)$asset->contract_address)!==0)throw new DomainException('TOKEN_CONTRACT_MISMATCH');
   if($data->tokenDecimals!==null && $data->tokenDecimals!==(int)$asset->decimals)throw new DomainException('TOKEN_DECIMALS_MISMATCH');
   if(bccomp($data->amountAtomic,'0',0)<=0)throw new DomainException('INVALID_TRANSFER_AMOUNT');
   $address=WalletAddress::with('invoice')->where('network',$asset->network)->whereRaw('LOWER(address)=?', [strtolower($data->toAddress)])->lockForUpdate()->first();
   if(!$address)throw new DomainException('UNALLOCATED_DEPOSIT');
   $invoice=$address->invoice; if(!$invoice||$invoice->pay_asset_id!==$asset->id||$invoice->environment!==$address->environment)throw new DomainException('INVOICE_ASSET_OR_ENVIRONMENT_MISMATCH');
   $tx=ChainTransaction::updateOrCreate(['network'=>$data->network,'tx_hash'=>$data->hash,'transfer_index'=>$data->transferIndex],[ 'asset_id'=>$asset->id,'from_address'=>$data->fromAddress,'to_address'=>$data->toAddress,'contract_address'=>$data->contractAddress,'token_decimals'=>$data->tokenDecimals,'amount_atomic'=>$data->amountAtomic,'block_height'=>$data->blockHeight,'confirmations'=>$data->confirmations,'finality_status'=>$data->status,'detected_at'=>$data->detectedAt??now() ]);
   InvoiceTransaction::firstOrCreate(['invoice_id'=>$invoice->id,'chain_transaction_id'=>$tx->id],['allocated_amount_atomic'=>$data->amountAtomic]);
   $paid=(string)InvoiceTransaction::where('invoice_id',$invoice->id)->join('chain_transactions','chain_transactions.id','=','invoice_transactions.chain_transaction_id')->sum('invoice_transactions.allocated_amount_atomic');
   $invoice->update(['paid_amount_atomic'=>$paid]);
   if($invoice->status==='waiting'){$this->states->transition($invoice,PaymentStatus::Confirming);$this->webhooks->dispatch($invoice->merchant_id,$invoice->environment,'payment.confirming',['payment_id'=>$invoice->public_id,'order_id'=>$invoice->order_id,'status'=>'confirming','pay_currency'=>$asset->code,'actually_paid'=>AtomicAmount::toDecimal($paid,$asset->decimals),'tx_hash'=>$tx->tx_hash]);}
   PaymentDetected::dispatch($invoice->fresh(),$tx);
   RefreshTransactionFinalityJob::dispatch($tx->id)->onQueue('confirmations')->afterCommit();
   return $tx;
  },5);
 }
}
