<?php

declare(strict_types=1);

namespace App\Mail;

use App\Models\Payment;
use App\Models\Communication;
use App\Models\Job;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Mail\Mailables\Attachment as MailAttachment;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\View;
use Symfony\Component\Mime\Email as SymfonyEmail;

class PaymentRequestMail extends Mailable
{
    use Queueable, SerializesModels;

    /** Optional context objects */
    protected ?Payment $payment = null;
    protected ?Communication $comm = null;
    protected ?Job $job = null;

    /**
     * Extra data (logoUrl, subject override, note/message, checkoutUrl, payUrl, etc.)
     * @var array<string, mixed>
     */
    protected array $data = [];

    /**
     * Flexible constructor.
     *
     * Usage A (existing): new PaymentRequestMail(Payment|Communication|Job $source, array $data = [])
     * Usage B (simple):   new PaymentRequestMail(Job $job, string|null $note)
     *
     * The 2nd parameter accepts either an array of options or a string note.
     *
     * @param  Payment|Communication|Job  $source
     * @param  array<string,mixed>|string|null  $dataOrNote
     */
    public function __construct(Payment|Communication|Job $source, array|string|null $dataOrNote = null)
    {
        // Resolve primary source
        if ($source instanceof Payment) {
            $this->payment = $source->loadMissing('job.booking.customer');
            $this->job     = $this->payment->job;
        } elseif ($source instanceof Communication) {
            $this->comm = $source->loadMissing('job.flow', 'job.booking.customer');
            $this->job  = $this->comm->job;
        } else {
            $this->job = $source->loadMissing('flow', 'booking.customer');
        }

        // Normalise the 2nd argument:
        // - If array, treat as options
        // - If string, treat as a note/message convenience
        if (is_array($dataOrNote)) {
            $this->data = $dataOrNote;
        } elseif (is_string($dataOrNote)) {
            $this->data = ['note' => $dataOrNote];
        } else {
            $this->data = [];
        }

        // Add trace/debug headers (Message-ID, entity refs, etc.)
        $this->withSymfonyMessage(function (SymfonyEmail $message) {
            $domain  = parse_url(config('app.url') ?: 'https://payments.example.com', PHP_URL_HOST) ?: 'payments.example.com';
            $headers = $message->getHeaders();

            if ($this->payment?->id) {
                $headers->addIdHeader('Message-ID', sprintf('payment-%d@%s', (int) $this->payment->id, $domain));
                $headers->addTextHeader('X-Payment-ID', (string) $this->payment->id);
            } elseif ($this->comm?->id) {
                $headers->addIdHeader('Message-ID', sprintf('comm-%d@%s', (int) $this->comm->id, $domain));
                $headers->addTextHeader('X-Comm-ID', (string) $this->comm->id);
                // Keep SendGrid-style unique args if you use them
                $headers->addTextHeader('X-SMTPAPI', json_encode([
                    'unique_args' => ['comm_id' => (string) $this->comm->id],
                ]));
            } elseif ($this->job?->id) {
                $headers->addIdHeader('Message-ID', sprintf('job-%d@%s', (int) $this->job->id, $domain));
            }

            if ($this->job?->id) {
                $headers->addTextHeader('X-Job-ID', (string) $this->job->id);
                $headers->addTextHeader('X-Entity-Ref-ID', (string) $this->job->id);
            }
        });
    }

    /* -----------------------------------------------------------------
     | Laravel 10+ API
     |------------------------------------------------------------------*/

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: $this->resolveSubject(),
        );
    }

    public function content(): Content
    {
        $checkoutUrl       = $this->resolveCheckoutOrPayUrl();
        $linkedAttachments = $this->buildLinkedAttachments();
        $view              = $this->resolveView();
        $text              = $this->resolveTextView();

        $vars = [
            'payment'         => $this->payment,
            'job'             => $this->job,
            'comm'            => $this->comm,
            'checkoutUrl'     => $checkoutUrl,
            'payUrl'          => $checkoutUrl, // alias for older blades
            'attachments'     => $linkedAttachments,
            'note'            => $this->data['note'] ?? $this->data['message'] ?? null,
            'logoUrl'         => $this->data['logoUrl'] ?? asset('storage/branding/logo.png'),
            'subject'         => $this->resolveSubject(),
            'reference'       => $this->resolveReference($this->job),
            'amountFormatted' => $this->formatAmountNZD($this->payment?->amount_cents),
            'reason'          => trim((string) ($this->payment?->reason ?? 'One-off charge')),
        ];

        return new Content(
            view: $view,
            text: $text,
            with: $vars
        );
    }

    public function attachments(): array
    {
        // Optionally attach a Job agreement PDF
        if ($this->job && !empty($this->job->agreement_path)) {
            return [
                MailAttachment::fromStorageDisk('agreements', $this->job->agreement_path)
                    ->as('Rental Agreement.pdf')
                    ->withMime('application/pdf'),
            ];
        }
        return [];
    }

    /* -----------------------------------------------------------------
     | Backward compatibility (older code paths using build())
     |------------------------------------------------------------------*/

    /**
     * If anything still calls ->build(), keep parity with envelope()/content().
     * Laravel 10 will prefer envelope/content, but this avoids fatal errors.
     */
    public function build(): self
    {
        $checkoutUrl       = $this->resolveCheckoutOrPayUrl();
        $linkedAttachments = $this->buildLinkedAttachments();
        $view              = $this->resolveView();
        $text              = $this->resolveTextView();

        $vars = [
            'payment'         => $this->payment,
            'job'             => $this->job,
            'comm'            => $this->comm,
            'checkoutUrl'     => $checkoutUrl,
            'payUrl'          => $checkoutUrl,
            'attachments'     => $linkedAttachments,
            'note'            => $this->data['note'] ?? $this->data['message'] ?? null,
            'logoUrl'         => $this->data['logoUrl'] ?? asset('storage/branding/logo.png'),
            'subject'         => $this->resolveSubject(),
            'reference'       => $this->resolveReference($this->job),
            'amountFormatted' => $this->formatAmountNZD($this->payment?->amount_cents),
            'reason'          => trim((string) ($this->payment->reason ?? 'One-off charge')),
        ];

        $mail = $this->subject($this->resolveSubject())
            ->view($view)
            ->with($vars);

        if ($text) {
            $mail->text($text, $vars);
        }

        if ($this->job && !empty($this->job->agreement_path)) {
            $mail->attachFromStorageDisk('agreements', $this->job->agreement_path, 'Rental Agreement.pdf', [
                'mime' => 'application/pdf',
            ]);
        }

        return $mail;
    }

    /* -----------------------------------------------------------------
     | Helpers
     |------------------------------------------------------------------*/

    /** Subject: prefer explicit override; then Payment-based; else Job/Booking-based */
    protected function resolveSubject(): string
    {
        if (!empty($this->data['subject'])) {
            return (string) $this->data['subject'];
        }

        if ($this->payment) {
            $reason = trim((string) ($this->payment->reason ?? 'One-off charge'));
            return 'Payment Request: ' . $this->formatAmountNZD($this->payment->amount_cents) . ' – ' . $reason;
        }

        if ($this->comm?->subject) {
            return (string) $this->comm->subject;
        }

        $ref = $this->resolveReference($this->job);
        return "Payment required for rental reservation {$ref}";
    }

    /** Prefer external_reference; then booking.reference; then Job #id; else generic. */
    protected function resolveReference(?Job $job): string
    {
        if (!empty($job?->external_reference)) {
            return (string) $job->external_reference;
        }
        if ($job?->booking && !empty($job->booking->reference)) {
            return (string) $job->booking->reference;
        }
        return $job ? ('Job #' . $job->getKey()) : 'Payment Request';
    }

    /** Determine a pay/checkout URL from (priority) data → Payment → Job routes. */
    protected function resolveCheckoutOrPayUrl(): ?string
    {
        // 1) Explicit overrides
        if (!empty($this->data['checkoutUrl'])) {
            return (string) $this->data['checkoutUrl'];
        }
        if (!empty($this->data['payUrl'])) {
            return (string) $this->data['payUrl'];
        }

        // 2) If you store a public token/session on Payment, construct here if desired.

        // 3) Job-powered fallbacks
        if ($this->job) {
            if (method_exists($this->job, 'payUrl')) {
                try {
                    $url = $this->job->payUrl();
                    if ($url) {
                        return $url;
                    }
                } catch (\Throwable) {
                    // ignore and continue to routes
                }
            }
            if (Route::has('portal.pay.show.job')) {
                return route('portal.pay.show.job', ['job' => $this->job]);
            }
            if (Route::has('portal.pay.show')) {
                return route('portal.pay.show', $this->job);
            }
            if (Route::has('portal.pay.show.token') && !empty($this->job->pay_token)) {
                return route('portal.pay.show.token', ['token' => $this->job->pay_token]);
            }
        }

        return null;
    }

    /** Build link metadata for files stored on the Payment (if any). */
    protected function buildLinkedAttachments(): array
    {
        if (!$this->payment) {
            return [];
        }

        try {
            $files = $this->payment->attachments()->get();
        } catch (\Throwable $e) {
            Log::warning('PaymentRequestMail: unable to load attachments', [
                'payment_id' => $this->payment->id ?? null,
                'error'      => $e->getMessage(),
            ]);
            return [];
        }

        return $files
            ->map(function ($a) {
                $url = null;

                // Try public URL first
                try {
                    $url = Storage::disk($a->disk)->url($a->path);
                } catch (\Throwable $e) {
                    // If not public, try temporaryUrl if supported (e.g., S3)
                    $disk = Storage::disk($a->disk);
                    if (method_exists($disk, 'temporaryUrl')) {
                        try {
                            $url = $disk->temporaryUrl($a->path, now()->addHours(48));
                        } catch (\Throwable) {
                            $url = null;
                        }
                    }
                }

                return [
                    'name' => $a->original_name ?: basename($a->path),
                    'url'  => $url,
                    'mime' => $a->mime ?? null,
                    'size' => (int) ($a->size_bytes ?? 0),
                ];
            })
            ->filter(fn ($f) => !empty($f['url']))
            ->values()
            ->all();
    }

    /** Choose HTML blade. */
    protected function resolveView(): string
    {
        if (View::exists('mail.payments.request')) {
            return 'mail.payments.request';
        }
        if (View::exists('emails.payment-request')) {
            return 'emails.payment-request';
        }
        return 'emails.generic-error';
    }

    /** Choose plain-text fallback blade if available. */
    protected function resolveTextView(): ?string
    {
        if (View::exists('mail.payments.request_plain')) {
            return 'mail.payments.request_plain';
        }
        if (View::exists('emails.payment-request_plain')) {
            return 'emails.payment-request_plain';
        }
        return null;
    }

    /** Format cents to NZD — uses your helper if present, else local formatter. */
    protected function formatAmountNZD(?int $amountCents): string
    {
        if (function_exists('money_format_nzd')) {
            return money_format_nzd($amountCents ?? 0);
        }
        $amount = number_format(($amountCents ?? 0) / 100, 2, '.', ',');
        return 'NZ$' . $amount;
    }
}
