<?php

namespace App\Domain\WhatsApp;

use App\Domain\WhatsApp\Contracts\WhatsAppDriver;
use App\Domain\WhatsApp\Drivers\LogDriver;
use App\Domain\WhatsApp\Drivers\MetaDriver;
use App\Domain\WhatsApp\Drivers\TwilioDriver;
use App\Enums\WaMessageStatus;
use App\Jobs\SendWhatsAppMessage;
use App\Models\Patient;
use App\Models\Tenant;
use App\Models\WaMessage;
use App\Support\Tenancy;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\URL;

/**
 * The WhatsApp engine: queue → send via driver → track status → retry.
 *
 * Every send is persisted first (conversation log), then dispatched to the
 * queue so the request never blocks on the provider. Failures are recorded
 * with code + reason; retryable failures are rescheduled with backoff.
 */
class WhatsAppManager
{
    public function __construct(
        protected TemplateRenderer $renderer,
        protected Tenancy $tenancy,
    ) {
    }

    public function driver(): WhatsAppDriver
    {
        return match (config('whatsapp.driver')) {
            'meta' => new MetaDriver(),
            'twilio' => new TwilioDriver(),
            default => new LogDriver(),
        };
    }

    /**
     * Build a message from a template and queue it. Returns the persisted
     * WaMessage (status=queued) or null when the template/patient opts out.
     */
    public function queueTemplate(
        Patient $patient,
        string $templateKey,
        array $tokens = [],
        ?Model $related = null,
        ?string $mediaPath = null,
        ?string $mediaMime = null,
        ?string $mediaFilename = null,
        ?int $createdBy = null,
        bool $force = false,
    ): ?WaMessage {
        if (! $force && ! $patient->whatsapp_opt_in) {
            return null;
        }

        $tenant = $patient->tenant ?? $this->tenancy->get();
        $locale = $patient->tenant?->default_locale ?? 'ar';

        $tokens = array_merge([
            'clinic' => $tenant->name,
            'patient' => $patient->full_name,
        ], $tokens);

        [$body, $enabled] = $this->renderer->render($tenant, $templateKey, $tokens, $locale);
        if (! $enabled || trim($body) === '') {
            return null;
        }

        return $this->queueRaw(
            patient: $patient,
            body: $body,
            templateKey: $templateKey,
            related: $related,
            mediaPath: $mediaPath,
            mediaMime: $mediaMime,
            mediaFilename: $mediaFilename,
            createdBy: $createdBy,
        );
    }

    /** Queue an already-rendered message (used by manual sends + templates). */
    public function queueRaw(
        Patient $patient,
        string $body,
        ?string $templateKey = null,
        ?Model $related = null,
        ?string $mediaPath = null,
        ?string $mediaMime = null,
        ?string $mediaFilename = null,
        ?int $createdBy = null,
    ): WaMessage {
        $message = new WaMessage([
            'patient_id' => $patient->id,
            'to_phone' => $patient->phone_normalized,
            'template_key' => $templateKey,
            'body' => $body,
            'status' => WaMessageStatus::Queued,
            'provider' => config('whatsapp.driver'),
            'media_path' => $mediaPath,
            'media_mime' => $mediaMime,
            'media_filename' => $mediaFilename,
            'created_by' => $createdBy,
        ]);
        $message->tenant_id = $patient->tenant_id;
        if ($related) {
            $message->related()->associate($related);
        }
        $message->save();

        SendWhatsAppMessage::dispatch($message->id);

        return $message;
    }

    /** Staff-facing message (no patient row) — billing/expiry alerts. */
    public function queueSystemMessage(\App\Models\Tenant $tenant, string $phone, string $body): WaMessage
    {
        $message = new WaMessage([
            'to_phone' => \App\Models\Patient::normalizePhone($phone),
            'body' => $body,
            'status' => WaMessageStatus::Queued,
            'provider' => config('whatsapp.driver'),
        ]);
        $message->tenant_id = $tenant->id;
        $message->save();

        if ($patient->tenant) {
            app(\App\Domain\Subscription\UsageService::class)
                ->increment($patient->tenant, 'wa_messages');
        }

        SendWhatsAppMessage::dispatch($message->id);

        return $message;
    }

    /**
     * Perform the actual send (called from the job). Updates status, provider
     * id, and on failure either schedules a retry or marks failed.
     */
    public function deliver(WaMessage $message): void
    {
        if ($message->status->isTerminal()) {
            return;
        }

        $driver = $this->driver();
        $message->forceFill(['attempts' => $message->attempts + 1])->save();

        $result = $message->hasMedia()
            ? $driver->sendDocument($message, $this->signedMediaUrl($message))
            : $driver->sendText($message);

        if ($result->ok) {
            $message->update([
                'status' => WaMessageStatus::Sent,
                'provider_message_id' => $result->providerMessageId,
                'sent_at' => now(),
                'failure_code' => null,
                'failure_reason' => null,
                'next_attempt_at' => null,
            ]);

            return;
        }

        $this->handleFailure($message, $result);
    }

    protected function handleFailure(WaMessage $message, SendResult $result): void
    {
        $max = (int) config('whatsapp.max_attempts');
        $canRetry = $result->retryable && $message->attempts < $max;

        if ($canRetry) {
            $backoff = config('whatsapp.retry_backoff_minutes');
            $minutes = $backoff[$message->attempts - 1] ?? end($backoff);

            $message->update([
                'status' => WaMessageStatus::Retrying,
                'failure_code' => $result->failureCode,
                'failure_reason' => $result->failureReason,
                'next_attempt_at' => now()->addMinutes($minutes),
            ]);

            return;
        }

        $message->update([
            'status' => WaMessageStatus::Failed,
            'failure_code' => $result->failureCode,
            'failure_reason' => $result->failureReason,
            'next_attempt_at' => null,
        ]);
    }

    /** Re-dispatch all messages whose retry time has arrived (scheduler). */
    public function sweepRetries(): int
    {
        $count = 0;
        WaMessage::query()->withoutGlobalScope('tenant')
            ->dueForRetry()->limit(200)->get()
            ->each(function (WaMessage $m) use (&$count) {
                SendWhatsAppMessage::dispatch($m->id);
                $count++;
            });

        return $count;
    }

    /** Manual single retry (from the conversation log UI). */
    public function retry(WaMessage $message): void
    {
        if ($message->status !== WaMessageStatus::Failed) {
            return;
        }
        $message->update(['status' => WaMessageStatus::Retrying, 'next_attempt_at' => now()]);
        SendWhatsAppMessage::dispatch($message->id);
    }

    /** Apply a provider webhook status update (sent/delivered/read/failed). */
    public function applyStatusUpdate(string $providerMessageId, string $status, ?string $reason = null): void
    {
        $message = WaMessage::query()->withoutGlobalScope('tenant')
            ->where('provider_message_id', $providerMessageId)->first();
        if (! $message) {
            return;
        }

        match ($status) {
            'delivered' => $message->update(['status' => WaMessageStatus::Delivered, 'delivered_at' => now()]),
            'read' => $message->update(['status' => WaMessageStatus::Read, 'read_at' => now(), 'delivered_at' => $message->delivered_at ?? now()]),
            'failed', 'undelivered' => $message->update(['status' => WaMessageStatus::Failed, 'failure_reason' => $reason]),
            'sent' => $message->status === WaMessageStatus::Queued
                ? $message->update(['status' => WaMessageStatus::Sent, 'sent_at' => now()])
                : null,
            default => null,
        };
    }

    protected function signedMediaUrl(WaMessage $message): string
    {
        return URL::temporarySignedRoute(
            'wa.media',
            now()->addMinutes((int) config('whatsapp.media_ttl_minutes')),
            ['message' => $message->uuid],
        );
    }
}
