<?php
namespace App\Jobs;
use App\Application\Services\Webhooks\{WebhookDestinationValidator,WebhookRetryPolicy};
use App\Models\{OperationsAlert,WebhookDelivery,WebhookDeliveryAttempt};
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\{Cache,Http};
use Throwable;
final class DeliverMerchantWebhookJob implements ShouldQueue {
 use Dispatchable,InteractsWithQueue,Queueable,SerializesModels;
 public int $tries=1;public int $timeout=30;
 public function __construct(public int $deliveryId){$this->onQueue('merchant-webhooks');}
 public function handle(WebhookRetryPolicy $policy,WebhookDestinationValidator $validator):void{
  $lock=Cache::lock("webhook-delivery:{$this->deliveryId}",60);if(!$lock->get())return;
  try{$d=WebhookDelivery::with('endpoint')->findOrFail($this->deliveryId);if(in_array($d->status,['delivered','exhausted'],true))return;
   $attempt=$d->attempt_count+1;$delay=$policy->delayForAttempt($attempt);if($delay===null){$this->exhaust($d,'Maximum webhook attempts reached.');return;}
   $raw=$d->raw_payload ?: json_encode($d->payload,JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR);if($d->raw_payload===null)$d->update(['raw_payload'=>$raw]);
   $timestamp=(string)now()->timestamp;$sig=hash_hmac('sha256',$timestamp.'.'.$raw,$d->endpoint->secret_ciphertext);$started=microtime(true);$status=null;$responseBody=null;$error=null;
   try{
    $validator->validate($d->endpoint->url,$d->endpoint->environment==='live');
    $res=Http::connectTimeout(config('gateway.webhooks.connect_timeout_seconds',3))->timeout(config('gateway.webhooks.read_timeout_seconds',10))->withOptions(['allow_redirects'=>$d->endpoint->environment!=='live'])->withHeaders(['Content-Type'=>'application/json','X-Gateway-Event-Id'=>$d->event_id,'X-Gateway-Timestamp'=>$timestamp,'X-Gateway-Signature'=>'v1='.$sig])->withBody($raw,'application/json')->post($d->endpoint->url);
    $status=$res->status();$responseBody=substr($res->body(),0,config('gateway.webhooks.max_response_bytes',65536));
    if($status<200||$status>=300)$error='Webhook returned HTTP '.$status;
   }catch(Throwable $e){$error=$e->getMessage();}
   $duration=(int)round((microtime(true)-$started)*1000);WebhookDeliveryAttempt::create(['delivery_id'=>$d->id,'attempt_number'=>$attempt,'timestamp'=>$timestamp,'signature'=>'v1='.$sig,'request_body'=>$raw,'response_code'=>$status,'response_body'=>$responseBody,'error'=>$error,'duration_ms'=>$duration,'attempted_at'=>now()]);
   $accepted=$status!==null&&$status>=200&&$status<300;$nextDelay=$policy->delayForAttempt($attempt+1);$d->update(['attempt_count'=>$attempt,'signature'=>'v1='.$sig,'last_response_code'=>$status,'last_error'=>$error,'status'=>$accepted?'delivered':($nextDelay===null?'exhausted':'retrying'),'delivered_at'=>$accepted?now():null,'exhausted_at'=>!$accepted&&$nextDelay===null?now():null,'next_attempt_at'=>$accepted||$nextDelay===null?null:now()->addSeconds($nextDelay)]);
   if(!$accepted){$this->raiseFailureAlert($d->fresh(),$error ?: 'Webhook delivery failed.');if($nextDelay===null)$this->exhaust($d,$error ?: "Webhook returned HTTP {$status}");}
  }finally{optional($lock)->release();}
 }
 private function raiseFailureAlert(WebhookDelivery $d,string $reason):void{OperationsAlert::updateOrCreate(['code'=>'MERCHANT_WEBHOOK_DELIVERY_FAILED','subject_type'=>'webhook_delivery','subject_id'=>(string)$d->id,'acknowledged_at'=>null],['severity'=>$d->status==='exhausted'?'critical':'warning','message'=>$reason,'context'=>['event_id'=>$d->event_id,'endpoint_id'=>$d->endpoint_id,'status'=>$d->status,'attempt_count'=>$d->attempt_count,'last_response_code'=>$d->last_response_code]]);}
 private function exhaust(WebhookDelivery $d,string $reason):void{$d->update(['status'=>'exhausted','exhausted_at'=>now(),'next_attempt_at'=>null,'last_error'=>$reason]);OperationsAlert::updateOrCreate(['code'=>'MERCHANT_WEBHOOK_EXHAUSTED','subject_type'=>'webhook_delivery','subject_id'=>(string)$d->id,'acknowledged_at'=>null],['severity'=>'critical','message'=>$reason,'context'=>['event_id'=>$d->event_id,'endpoint_id'=>$d->endpoint_id]]);}
}