<?php
// app/Jobs/SendPaymentReceipt.php

declare(strict_types=1);

namespace App\Listeners;

use App\Mail\PaymentReceiptMail; // <-- uses your Mailable for the receipt
use App\Models\Payment;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Throwable;

class SendPaymentReceiptListener implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /** @var int max tries before failing permanently */
    public int $tries = 6;

    /** @var int seconds before job is considered failed if it times out */
    public int $timeout = 30;

    /** @var Payment */
    public Payment $payment;

    /** Optional override for recipient email(s) */
    public ?array $to = null;

    /**
     * @param  Payment        $payment  A Payment model that has succeeded (status === 'succeeded')
     * @param  string|array|null $to    Optional email or list of emails to send receipt to (defaults to customer email)
     */
    public function __construct(Payment $payment, string|array|null $to = null)
    {
        $this->payment = $payment->withoutRelations();
        $this->to      = is_string($to) ? [$to] : $to;

        // Send sooner rather than later
        $this->onQueue('mail');
    }

    /**
     * Rate limit & avoid duplicate sends for the same payment.
     */
    public function middleware(): array
    {
        $key = 'receipt-payment-'.$this->payment->getKey();

        return [
            // Prevent concurrent duplicates for same payment id
            new WithoutOverlapping($key),
            // Be gentle if many receipts fire at once
            (new RateLimited('emails'))->dontRelease(), // uses rate limiter "emails" if configured
        ];
    }

    /**
     * Exponential backoff between retries.
     */
    public function backoff(): array
    {
        return [5, 15, 30, 60, 120, 300];
    }

    public function handle(): void
    {
        // Reload with relations we need, but keep it lean
        $p = Payment::query()
            ->with([
                'job:id,reference',
                'job.booking:id,job_id,customer_id',
                'job.booking.customer:id,email,name',
                'attachments', // morphMany: file attachments to include in email body (links) or as attachments if desired
            ])
            ->findOrFail($this->payment->getKey());

        // Guard: only send for succeeded payments
        if ($p->status !== 'succeeded') {
            Log::info('SendPaymentReceipt skipped: payment not succeeded', [
                'payment_id' => $p->id, 'status' => $p->status,
            ]);
            return;
        }

        // Idempotency: skip if we already recorded a successful send recently
        $details = (array) $p->details;
        $alreadySentAt = Arr::get($details, 'receipt.sent_at');
        if ($alreadySentAt) {
            Log::info('SendPaymentReceipt skipped: already sent', [
                'payment_id' => $p->id, 'sent_at' => $alreadySentAt,
            ]);
            return;
        }

        // Resolve recipients
        $recipients = $this->to;
        if (!$recipients) {
            $customerEmail = optional(optional($p->job)->booking)->customer->email ?? null;
            // Fallback to any meta-provided email
            $metaEmail     = Arr::get($p->meta ?? [], 'email');
            $recipients    = array_values(array_filter([$customerEmail, $metaEmail]));
        }

        if (empty($recipients)) {
            Log::warning('SendPaymentReceipt aborted: no recipient email found', ['payment_id' => $p->id]);
            return; // nothing to do
        }

        // Build and send the receipt email
        try {
            // Your PaymentReceiptMail should accept the Payment model
            Mail::to($recipients)->send(new PaymentReceiptMail($p));

            // Record send event (idempotency footprint)
            $now = now()->toIso8601String();
            $details['receipt'] = array_merge([
                'sent_at'   => $now,
                'recipients'=> $recipients,
            ], (array) Arr::get($details, 'receipt', []));

            $p->forceFill(['details' => $details])->save();

            Log::info('Payment receipt email sent', [
                'payment_id' => $p->id,
                'recipients' => $recipients,
                'job_ref'    => optional($p->job)->reference,
            ]);
        } catch (Throwable $e) {
            // Let the job retry, but log details
            Log::error('Failed to send payment receipt email', [
                'payment_id' => $p->id,
                'recipients' => $recipients,
                'error'      => $e->getMessage(),
            ]);
            throw $e;
        }
    }

    /**
     * Called after all retries are exhausted.
     */
    public function failed(Throwable $e): void
    {
        Log::critical('SendPaymentReceipt permanently failed', [
            'payment_id' => $this->payment->getKey(),
            'error'      => $e->getMessage(),
        ]);
    }
}
