<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Enums\PaymentStatus;
use App\Models\Job;
use App\Models\Communication;
use App\Models\CommunicationEvent;
use App\Models\JobEvent;
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;

// Optional mailables (guarded via class_exists)
use App\Mail\PaymentRequestMail;
use App\Mail\PaymentReceiptMail;
use App\Mail\HoldPlacedMail;
use App\Mail\HoldReleasedMail;

// For precise invalid-email error handling
use Symfony\Component\Mime\Exception\RfcComplianceException;

class JobController extends Controller
{
    /**
     * ROUTE HANDLER
     * GET /admin/jobs
     * (Adds paid_sum_cents via withSum using PaymentStatus enum)
     */
    public function index(Request $request)
    {
        $jobs = Job::query()
            ->withSum(
                ['payments as paid_sum_cents' => function ($q) {
                    $q->whereIn('status', [
                        PaymentStatus::Succeeded->value,
                        PaymentStatus::Captured->value,
                    ]);
                }],
                'amount_cents'
            )
            ->latest()
            ->paginate(50);

        if ($request->expectsJson()) {
            return response()->json($jobs);
        }

        return view('admin.jobs.index', compact('jobs'));
    }

    /**
     * ROUTE HANDLER (instance method)
     * POST /admin/jobs/{job}/email-payment-request
     */
    public function emailPaymentRequest(Request $request, Job $job): JsonResponse|RedirectResponse
    {
        $data = $request->validate([
            'to'           => ['nullable', 'email'],
            'cc'           => ['nullable', 'email'],
            'bcc'          => ['nullable', 'email'],
            'subject'      => ['nullable', 'string', 'max:150'],
            'message'      => ['nullable', 'string', 'max:4000'],
            'amount_cents' => ['nullable', 'integer', 'min:0'],
            'type'         => ['nullable', 'string', 'in:deposit,balance,total,custom'],
        ]);

        [$ok, $msg] = $this->sendPaymentRequestInternal($job, $data);

        return $this->respond($request, $ok ? 200 : 500, $msg);
    }

    /**
     * STATIC WRAPPER (callable from jobs/commands/other controllers)
     */
    public static function emailPaymentRequestStatic(Job $job, array $data = []): array
    {
        try {
            $self = app(static::class);
            [$ok, $msg] = $self->sendPaymentRequestInternal($job, $data);

            if (!$ok) {
                Log::error('emailPaymentRequestStatic failed', [
                    'job_id' => $job->getKey(),
                    'msg'    => $msg,
                ]);
            }

            return [$ok, $msg];
        } catch (\Throwable $e) {
            Log::error('emailPaymentRequestStatic threw', [
                'job_id' => $job->getKey(),
                'err'    => $e->getMessage(),
            ]);
            return [false, 'Mailer threw: ' . $e->getMessage()];
        }
    }

    /* ============================== MAIL HELPERS ============================== */

    private function sendPaymentRequestInternal(Job $job, array $data): array
    {
        $job->loadMissing('booking.customer', 'flow');

        // Resolve recipient
        $to = $data['to']
            ?? optional($job->booking?->customer)->email
            ?? $job->customer_email
            ?? null;

        $to = is_string($to) ? trim($to) : '';
        if ($to === '' || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
            return [false, 'No valid recipient email address found. Provide `to`, or set a valid email on the booking/customer or job.'];
        }

        $payUrl = method_exists($job, 'payUrl')
            ? (string) $job->payUrl($data['amount_cents'] ?? null)
            : (string) URL::signedRoute('portal.pay.show.job', ['job' => $job->getKey()]);

        $currency  = $job->currency ?? 'NZD';
        $dueCents  = $data['amount_cents']
            ?? $job->remaining_cents
            ?? $job->amount_due_cents
            ?? $job->due_cents
            ?? null;
        $holdCents = $job->bond_cents ?? $job->hold_cents ?? null;

        $reference = optional($job->booking)->reference
            ?? $job->external_reference
            ?? ('Job #' . $job->getKey());

        $type    = $data['type'] ?? ($dueCents !== null ? 'total' : 'custom');
        $subject = $data['subject'] ?? "Payment required for rental reservation {$reference}";
        $note    = trim((string)($data['message'] ?? ''));

        $customerName = optional($job->booking?->customer)->first_name
            ?? optional($job->booking?->customer)->name
            ?? $job->customer_name
            ?? 'there';

        /** @var Communication|null $comm */
        $comm = null;

        try {
            if (class_exists(PaymentRequestMail::class)) {
                $comm = Communication::create([
                    'job_id'   => $job->getKey(),
                    'channel'  => 'email',
                    'type'     => 'payment_request',
                    'to_email' => $to,
                    'subject'  => $subject,
                    'status'   => 'queued',
                    'meta'     => [
                        'reference'     => $reference,
                        'currency'      => $currency,
                        'amount_cents'  => $dueCents,
                        'hold_cents'    => $holdCents,
                        'request_type'  => $type,
                    ],
                ]);

                $payload = [
                    'subject'      => $subject,
                    'payUrl'       => $payUrl,
                    'reference'    => $reference,
                    'currency'     => $currency,
                    'note'         => $note,
                    'holdCents'    => $holdCents,
                    'amount_cents' => $dueCents,
                    'type'         => $type,
                ];

                $mailable = new PaymentRequestMail($comm, $payload);

                $pending = Mail::to($to);
                if (!empty($data['cc']))  { $pending->cc($data['cc']); }
                if (!empty($data['bcc'])) { $pending->bcc($data['bcc']); }
                $pending->send($mailable->subject($subject));

                $comm->update(['status' => 'sent']);
                CommunicationEvent::create([
                    'communication_id' => $comm->id,
                    'type'             => 'sent',
                    'payload'          => ['to' => $to, 'subject' => $subject],
                ]);

                $this->logJobEvent($job, 'email.sent', [
                    'comm_id' => $comm->id,
                    'to'      => $to,
                    'subject' => $subject,
                ]);
            } else {
                // Fallback HTML-only email
                $html = $this->buildEmailHtml(
                    customerName: $customerName,
                    reference: $reference,
                    currency: $currency,
                    dueCents: $dueCents,
                    holdCents: $holdCents,
                    payUrl: $payUrl,
                    note: $note,
                );

                Mail::html($html, function ($m) use ($to, $subject, $data) {
                    $m->to($to)->subject($subject);
                    if (!empty($data['cc']))  { $m->cc($data['cc']); }
                    if (!empty($data['bcc'])) { $m->bcc($data['bcc']); }
                });

                $this->logJobEvent($job, 'email.sent', [
                    'to'      => $to,
                    'subject' => $subject,
                ]);
            }

            Log::info('Payment request email sent', [
                'job_id'  => $job->getKey(),
                'to'      => $to,
                'subject' => $subject,
                'amount'  => $dueCents,
                'type'    => $type,
            ]);

            return [true, "Payment request sent to {$to}."];
        } catch (RfcComplianceException $e) {
            if ($comm) {
                $comm->update(['status' => 'failed']);
                CommunicationEvent::create([
                    'communication_id' => $comm->id,
                    'type'             => 'failed',
                    'payload'          => [
                        'error' => 'Invalid recipient email',
                        'to'    => $to !== '' ? $to : '(empty)',
                        'msg'   => $e->getMessage(),
                    ],
                ]);
            }

            $this->logJobEvent($job, 'email.failed', [
                'reason' => 'invalid_recipient',
                'to'     => $to,
                'msg'    => $e->getMessage(),
            ]);

            Log::error('Payment request email failed due to invalid recipient', [
                'job_id' => $job->getKey(),
                'to'     => $to,
                'error'  => $e->getMessage(),
            ]);

            return [false, 'Failed to send email: recipient address is invalid.'];
        } catch (\Throwable $e) {
            if ($comm) {
                $comm->update(['status' => 'failed']);
                CommunicationEvent::create([
                    'communication_id' => $comm->id,
                    'type'             => 'failed',
                    'payload'          => ['error' => $e->getMessage()],
                ]);
            }

            $this->logJobEvent($job, 'email.failed', [
                'reason' => 'mailer_exception',
                'to'     => $to,
                'msg'    => $e->getMessage(),
            ]);

            Log::error('Payment request email failed', [
                'job_id' => $job->getKey(),
                'to'     => $to,
                'error'  => $e->getMessage(),
            ]);
            return [false, 'Failed to send email: ' . $e->getMessage()];
        }
    }

    /* ============================== NOTIFIERS ================================ */

    public static function notifyReceipt(Job $job, $payment): void
    {
        // … unchanged …
    }

    public static function notifyHoldPlaced(Job $job, int $holdCents, string $currency, ?string $releaseEta = 'usually 7–10 days'): void
    {
        // … unchanged …
    }

    public static function notifyHoldReleased(Job $job, int $holdCents, string $currency): void
    {
        // … unchanged …
    }

    /* ============================== UTILITIES ================================ */

    private function logJobEvent(Job $job, string $type, array $payload = []): void
    {
        try {
            JobEvent::create([
                'job_id'  => $job->getKey(),
                'type'    => $type,
                'payload' => $payload,
            ]);
        } catch (\Throwable $e) {
            Log::warning('Failed to log JobEvent', [
                'job_id' => $job->getKey(),
                'type'   => $type,
                'error'  => $e->getMessage(),
            ]);
        }
    }

    private function money(?int $cents, string $currency = 'NZD'): ?string
    {
        if ($cents === null) return null;
        $amount = number_format($cents / 100, 2, '.', ',');
        return "{$currency} {$amount}";
    }

    private function buildEmailHtml(
        string $customerName,
        string $reference,
        string $currency,
        ?int $dueCents,
        ?int $holdCents,
        string $payUrl,
        string $note = ''
    ): string {
        // … unchanged …
    }

    private function escape(string $value): string
    {
        return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }

    private function respond(Request $request, int $status, string $message): JsonResponse|RedirectResponse
    {
        if ($request->expectsJson()) {
            return response()->json([
                'ok'      => $status < 400,
                'message' => $message,
            ], $status);
        }
        return back()->with($status < 400 ? 'status' : 'error', $message);
    }
}
