<?php

namespace Modules\WhatsApp\Entities\Services;

use App\Business;
use App\Transaction;
use App\TransactionPayment;
use App\Utils\Util;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
use Modules\WhatsApp\Entities\WaSetting;
use Modules\WhatsApp\Entities\WaTemplate;

/**
 * Turns ERP/clinic events into WhatsApp messages. Each public method maps to one
 * event in WaEvents; the private dispatch() centralises the toggle check,
 * template resolution, rendering and hand-off to WaSender (persist + queue +
 * reconnect-safe delivery).
 *
 * Every method is best-effort and never throws into the caller — a notification
 * problem must never break a sale or an appointment save.
 */
class WaNotifier
{
    // ---------------------------------------------------------------- Sales

    /** A finalised sell invoice → notify the customer. */
    public function invoiceCreated(Transaction $transaction): void
    {
        $this->guard(function () use ($transaction) {
            $this->dispatch(
                (int) $transaction->business_id,
                'invoice',
                $transaction->contact->mobile ?? null,
                $transaction->location_id,
                'invoice:' . $transaction->id,
                fn (string $body) => $this->renderTransaction($transaction, $body)
            );
        }, $transaction->id ?? null);
    }

    /** A payment recorded against a sell invoice → confirm to the customer. */
    public function paymentReceived(TransactionPayment $payment): void
    {
        $this->guard(function () use ($payment) {
            $transaction = $payment->transaction;
            if (!$transaction || $transaction->type !== 'sell') {
                return;
            }

            $this->dispatch(
                (int) $transaction->business_id,
                'payment',
                $transaction->contact->mobile ?? null,
                $transaction->location_id,
                'payment:' . $payment->id,
                function (string $body) use ($transaction, $payment) {
                    // {received_amount} should reflect THIS payment, not the running total.
                    $body = str_replace('{received_amount}', number_format((float) $payment->amount, 2), $body);
                    return $this->renderTransaction($transaction, $body);
                }
            );
        }, $payment->id ?? null);
    }

    /** A finalised sell return → confirm the refund/return to the customer. */
    public function saleReturned(Transaction $transaction): void
    {
        $this->guard(function () use ($transaction) {
            // Render against the original invoice when we can, so {invoice_number}
            // and {invoice_url} point at the sale the customer recognises.
            $ref = $transaction;
            if (!empty($transaction->return_parent_id)) {
                $parent = Transaction::find($transaction->return_parent_id);
                if ($parent) {
                    $ref = $parent;
                }
            }

            $this->dispatch(
                (int) $transaction->business_id,
                'sale_return',
                ($ref->contact->mobile ?? $transaction->contact->mobile) ?? null,
                $transaction->location_id,
                'sale_return:' . $transaction->id,
                fn (string $body) => $this->renderTransaction($ref, $body)
            );
        }, $transaction->id ?? null);
    }

    /** A quotation created for a customer → share it. */
    public function quotationSent(Transaction $transaction): void
    {
        $this->guard(function () use ($transaction) {
            $this->dispatch(
                (int) $transaction->business_id,
                'quotation',
                $transaction->contact->mobile ?? null,
                $transaction->location_id,
                'quotation:' . $transaction->id,
                fn (string $body) => $this->renderTransaction($transaction, $body)
            );
        }, $transaction->id ?? null);
    }

    /** Overdue-invoice payment reminder (driven by the payment-reminders cron). */
    public function paymentDue(Transaction $transaction): void
    {
        $this->guard(function () use ($transaction) {
            $this->dispatch(
                (int) $transaction->business_id,
                'payment_due',
                $transaction->contact->mobile ?? null,
                $transaction->location_id,
                'payment_due:' . $transaction->id . ':' . now()->toDateString(),
                fn (string $body) => $this->renderTransaction($transaction, $body)
            );
        }, $transaction->id ?? null);
    }

    /** Shipping/delivery status changed on a sale. */
    public function shipmentStatus(Transaction $transaction): void
    {
        $this->guard(function () use ($transaction) {
            $status = ucwords(str_replace('_', ' ', (string) $transaction->shipping_status));
            $this->dispatch(
                (int) $transaction->business_id,
                'shipment',
                $transaction->contact->mobile ?? null,
                $transaction->location_id,
                'shipment:' . $transaction->id . ':' . $transaction->shipping_status,
                function (string $body) use ($transaction, $status) {
                    $body = str_replace('{shipping_status}', $status, $body);
                    return $this->renderTransaction($transaction, $body);
                }
            );
        }, $transaction->id ?? null);
    }

    /** A purchase order issued → notify the supplier. */
    public function poSent(Transaction $transaction): void
    {
        $this->guard(function () use ($transaction) {
            $this->dispatch(
                (int) $transaction->business_id,
                'po_sent',
                $transaction->contact->mobile ?? null,
                $transaction->location_id,
                'po_sent:' . $transaction->id,
                fn (string $body) => $this->renderTransaction($transaction, $body)
            );
        }, $transaction->id ?? null);
    }

    // -------------------------------------------------------- Onboarding

    /** A new customer contact was created → welcome them. */
    public function contactWelcome($contact): void
    {
        $this->guard(function () use ($contact) {
            $tags = [
                '{contact_name}'  => $contact->name ?? '',
                '{business_name}' => $this->businessName($contact->business_id),
            ];
            $this->dispatchMap((int) $contact->business_id, 'contact_welcome', $contact->mobile ?? null, null, 'contact_welcome:' . $contact->id, $tags);
        }, $contact->id ?? null);
    }

    /** A new pet owner (VcPetOwnerDetail) was created → welcome to the clinic. */
    public function petOwnerWelcome($detail): void
    {
        $this->guard(function () use ($detail) {
            $contact = \App\Contact::find($detail->contact_id);
            $name = $this->businessName($detail->business_id);
            $tags = [
                '{owner_name}'    => $contact->name ?? '',
                '{clinic_name}'   => $name,
                '{business_name}' => $name,
            ];
            $this->dispatchMap((int) $detail->business_id, 'pet_owner_welcome', $contact->mobile ?? null, null, 'pet_owner_welcome:' . $detail->contact_id, $tags);
        }, $detail->id ?? null);
    }

    /** A new pet was registered → welcome the pet. */
    public function petWelcome($pet): void
    {
        $this->guard(function () use ($pet) {
            $contact = $pet->owner ?? ($pet->contact_id ? \App\Contact::find($pet->contact_id) : null);
            $name = $this->businessName($pet->business_id);
            $tags = [
                '{owner_name}'    => $contact->name ?? '',
                '{pet_name}'      => $pet->petname ?? '',
                '{clinic_name}'   => $name,
                '{business_name}' => $name,
            ];
            $this->dispatchMap((int) $pet->business_id, 'pet_welcome', $contact->mobile ?? null, null, 'pet_welcome:' . $pet->id, $tags);
        }, $pet->id ?? null);
    }

    // ---------------------------------------------------------------- Lab

    /** Lab results captured for a sample → notify the pet owner. */
    public function labResultsReady($session): void
    {
        $this->guard(function () use ($session) {
            $contact = $session->contact_id ? \App\Contact::find($session->contact_id) : null;
            $mobile = $contact->mobile ?? null;
            $business = Business::find($session->business_id);

            $petName = $session->patient_name ?? '';
            if (empty($petName) && !empty($session->vc_pet_id) && class_exists(\Modules\VetClinic\Entities\VcPet::class)) {
                $pet = \Modules\VetClinic\Entities\VcPet::find($session->vc_pet_id);
                $petName = $pet->petname ?? '';
            }

            $tags = [
                '{owner_name}'    => $contact->name ?? '',
                '{pet_name}'      => $petName,
                '{business_name}' => $business->name ?? '',
                '{clinic_name}'   => $business->name ?? '',
                '{sample_no}'     => $session->sample_no ?? '',
                '{test_date}'     => (string) ($session->test_date ?? ''),
            ];

            $this->dispatchMap((int) $session->business_id, 'lab_results', $mobile, null, 'lab:' . $session->id, $tags);
        }, $session->id ?? null);
    }

    // -------------------------------------------------------- Appointments

    public function appointmentBooked($appointment): void
    {
        $this->guard(fn () => $this->dispatchAppointment($appointment, 'appointment_booked', 'appt_booked:' . $appointment->id), $appointment->id ?? null);
    }

    public function appointmentReminder($appointment): void
    {
        $this->guard(fn () => $this->dispatchAppointment($appointment, 'appointment_reminder', 'appt_reminder:' . $appointment->id), $appointment->id ?? null);
    }

    public function appointmentCancelled($appointment): void
    {
        $this->guard(fn () => $this->dispatchAppointment($appointment, 'appointment_cancelled', 'appt_cancelled:' . $appointment->id), $appointment->id ?? null);
    }

    public function appointmentRescheduled($appointment): void
    {
        // Include the new date/time so each reschedule is a distinct message.
        $key = 'appt_resched:' . $appointment->id . ':' . $appointment->appointment_date . ':' . $appointment->appointment_time;
        $this->guard(fn () => $this->dispatchAppointment($appointment, 'appointment_rescheduled', $key), $appointment->id ?? null);
    }

    public function appointmentFollowup($appointment): void
    {
        $this->guard(fn () => $this->dispatchAppointment($appointment, 'appointment_followup', 'appt_followup:' . $appointment->id), $appointment->id ?? null);
    }

    private function dispatchAppointment($appointment, string $eventKey, string $dedupeKey): void
    {
        $mobile = $appointment->owner->mobile ?? null;
        $this->dispatch(
            (int) $appointment->business_id,
            $eventKey,
            $mobile,
            null, // appointment location_id is a clinic location, not a business_location → use default channel
            $dedupeKey,
            fn (string $body) => $this->renderAppointment($appointment, $body)
        );
    }

    // -------------------------------------------------------------- Engine

    /**
     * Generic, map-rendered send used by cron commands (vaccination/birthday) and
     * the lab path: {tag} placeholders are substituted straight from $tags.
     * Self-guarded so callers don't need their own try/catch.
     */
    public function sendEvent(int $businessId, string $eventKey, ?string $mobile, $locationId, string $dedupeKey, array $tags): void
    {
        $this->guard(fn () => $this->dispatchMap($businessId, $eventKey, $mobile, $locationId, $dedupeKey, $tags), $dedupeKey);
    }

    private function dispatchMap(int $businessId, string $eventKey, ?string $mobile, $locationId, string $dedupeKey, array $tags): void
    {
        $this->dispatch($businessId, $eventKey, $mobile, $locationId, $dedupeKey, fn (string $body) => strtr($body, $tags));
    }


    /**
     * Shared send path: enabled + per-event toggle, resolve the chosen template,
     * render it, and queue it (deduped).
     */
    private function dispatch(int $businessId, string $eventKey, ?string $mobile, $locationId, string $dedupeKey, callable $render): void
    {
        $events = WaEvents::all();
        if (!isset($events[$eventKey])) {
            return;
        }
        $event = $events[$eventKey];

        if (WaSetting::get($businessId, 'enabled', '1') !== '1') {
            return;
        }
        // Honour the event's default_on when the business hasn't saved settings yet,
        // so opt-in (default-off) events stay silent until explicitly enabled.
        $toggleDefault = !empty($event['default_on']) ? '1' : '0';
        if (WaSetting::get($businessId, $event['toggle'], $toggleDefault) !== '1') {
            return;
        }
        if (empty($mobile)) {
            return;
        }

        DefaultTemplates::ensure($businessId);

        $templateName = WaSetting::get($businessId, $event['template'], $event['default']) ?: $event['default'];
        $template = WaTemplate::where('business_id', $businessId)
            ->where('name', $templateName)
            ->where('active', true)
            ->first();
        if (!$template) {
            return;
        }

        $body = $render($template->body);
        if (empty(trim((string) $body))) {
            return;
        }

        // Optional "Powered by TPS" footer on automated messages (default on,
        // per-business toggle, disable-able straight from the DB).
        if (WaSetting::get($businessId, 'powered_by', '1') === '1') {
            $footer = (string) config('whatsapp.powered_by_text', '');
            if ($footer !== '') {
                $body .= "\n\n_" . $footer . "_";
            }
        }

        (new WaSender())->send([
            'business_id' => $businessId,
            'to'          => $mobile,
            'body'        => $body,
            'location_id' => $locationId,
            'source'      => $eventKey,
            'dedupe_key'  => $dedupeKey,
        ]);
    }

    /** Render a body with the core transaction tag engine. */
    private function renderTransaction(Transaction $transaction, string $body): string
    {
        $rendered = (new Util())->replaceTags((int) $transaction->business_id, ['body' => $body], $transaction);
        return $rendered['body'] ?? $body;
    }

    /** Render a body with VetClinic appointment tags. */
    private function renderAppointment($appointment, string $body): string
    {
        $owner   = $appointment->owner ?? null;
        $pet     = $appointment->pet ?? null;
        $doctor  = $appointment->doctor ?? null;
        $loc     = $appointment->location ?? null;
        $business = Business::find($appointment->business_id);

        $date = $appointment->appointment_date;
        if ($date instanceof Carbon) {
            $date = $date->format('D, d M Y');
        }
        $time = $appointment->appointment_time;
        if (!empty($time)) {
            $time = substr((string) $time, 0, 5); // HH:MM
        }

        $map = [
            '{owner_name}'       => $owner->name ?? '',
            '{pet_name}'         => $pet->petname ?? '',
            '{appointment_date}' => (string) $date,
            '{appointment_time}' => (string) $time,
            '{doctor_name}'      => $doctor->name ?? '',
            '{location_name}'    => $loc->name ?? '',
            '{clinic_name}'      => ($loc->name ?? null) ?: ($business->name ?? ''),
            '{business_name}'    => $business->name ?? '',
            '{ref_no}'           => $appointment->ref_no ?? '',
            '{status}'           => $appointment->status ?? '',
        ];

        return strtr($body, $map);
    }

    private array $businessNameCache = [];

    private function businessName($businessId): string
    {
        $businessId = (int) $businessId;
        if (!isset($this->businessNameCache[$businessId])) {
            $this->businessNameCache[$businessId] = (string) (Business::where('id', $businessId)->value('name') ?? '');
        }
        return $this->businessNameCache[$businessId];
    }

    /** Run a notification closure, swallowing any error so callers are never affected. */
    private function guard(callable $fn, $contextId = null): void
    {
        try {
            $fn();
        } catch (\Throwable $e) {
            Log::warning('WhatsApp notify failed [' . ($contextId ?? '?') . ']: ' . $e->getMessage());
        }
    }
}
