<?php

namespace App\Services;

use App\Models\Order;
use App\Models\OrderCourierShipment;
use App\Models\OrderStatusSmsLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class OrderStatusSmsService
{
    public function __construct(
        private readonly OutboundSmsService $outboundSmsService
    ) {
    }

    public function sendForOrder(Order $order, string $status, mixed $message): bool
    {
        $normalizedStatus = $this->normalizeStatus($status);
        $notificationKey = $this->resolveNotificationKey($normalizedStatus);
        $courierShipment = null;

        if ($normalizedStatus === 'picked_up') {
            $courierShipment = $order->courierShipment;

            if ($courierShipment) {
                if (! $courierShipment->isCourierConfirmedOutForDelivery()) {
                    return false;
                }

                $message = $this->courierOutForDeliveryMessage($order, $courierShipment);
            }
        }

        if ($courierShipment) {
            return DB::transaction(function () use ($order, $courierShipment, $normalizedStatus, $notificationKey, $message) {
                $lockedShipment = OrderCourierShipment::query()
                    ->lockForUpdate()
                    ->find($courierShipment->id);

                if (! $lockedShipment || ! $lockedShipment->isCourierConfirmedOutForDelivery()) {
                    return false;
                }

                $freshOrder = Order::query()->with(['customer', 'guest'])->find($order->id) ?: $order;
                $freshOrder->setRelation('courierShipment', $lockedShipment);

                return $this->sendResolved(
                    order: $freshOrder,
                    normalizedStatus: $normalizedStatus,
                    notificationKey: $notificationKey,
                    message: $this->courierOutForDeliveryMessage($freshOrder, $lockedShipment),
                    courierShipment: $lockedShipment
                );
            });
        }

        return $this->sendResolved(
            order: $order,
            normalizedStatus: $normalizedStatus,
            notificationKey: $notificationKey,
            message: $message,
            courierShipment: null
        );
    }

    private function sendResolved(
        Order $order,
        string $normalizedStatus,
        ?string $notificationKey,
        mixed $message,
        ?OrderCourierShipment $courierShipment
    ): bool {
        $message = $this->sanitizeMessage($message);

        if (! $notificationKey || ! filled($message)) {
            return false;
        }

        // Courier-confirmed Out for Delivery SMS is a mandatory business event,
        // not an optional template notification. Other status SMS messages still
        // honor their Admin notification toggle.
        if (! $courierShipment
            && ! \App\CentralLogics\Helpers::getNotificationStatusData('customer', $notificationKey, 'sms_status')
        ) {
            return false;
        }

        if ($courierShipment) {
            if ($courierShipment->out_for_delivery_sms_sent_at) {
                return true;
            }

            if ((int) $courierShipment->out_for_delivery_sms_attempts >= 3) {
                return false;
            }

            if ($courierShipment->out_for_delivery_sms_next_retry_at?->isFuture()) {
                return false;
            }

            $courierShipment->fill([
                'out_for_delivery_sms_status' => 'sending',
                'out_for_delivery_sms_attempts' => (int) $courierShipment->out_for_delivery_sms_attempts + 1,
                'out_for_delivery_sms_last_attempt_at' => now(),
                'out_for_delivery_sms_last_error' => null,
                'out_for_delivery_sms_next_retry_at' => null,
            ])->save();
        }

        $phone = $this->resolvePhoneNumber($order);
        if (! filled($phone)) {
            $this->storeLog(
                order: $order,
                status: $normalizedStatus,
                notificationKey: $notificationKey,
                phone: null,
                message: $message,
                gateway: null,
                response: 'missing_phone',
                isSuccess: false,
                errorMessage: 'Customer phone number not found for SMS notification.'
            );

            if ($courierShipment) {
                $this->markCourierSmsFailure($courierShipment, 'Customer phone number not found for SMS notification.');
            }

            return false;
        }

        if (! $courierShipment && $this->hasSuccessfulLog($order->id, $normalizedStatus)) {
            return true;
        }

        $deliveryResult = $this->outboundSmsService->send($phone, $message);
        $response = (string) data_get($deliveryResult, 'response', 'error');
        $gateway = data_get($deliveryResult, 'gateway');
        $gatewayError = data_get($deliveryResult, 'error');

        $isSuccess = $response === 'success';

        $this->storeLog(
            order: $order,
            status: $normalizedStatus,
            notificationKey: $notificationKey,
            phone: $phone,
            message: $message,
            gateway: $gateway,
            response: $response,
            isSuccess: $isSuccess,
            errorMessage: $isSuccess
                ? null
                : ($gatewayError ?: 'Order status SMS could not be sent.')
        );

        if ($courierShipment) {
            if ($isSuccess) {
                $courierShipment->fill([
                    'out_for_delivery_sms_status' => 'sent',
                    'out_for_delivery_sms_sent_at' => now(),
                    'out_for_delivery_sms_last_error' => null,
                    'out_for_delivery_sms_next_retry_at' => null,
                ])->save();
            } else {
                $failureMessage = $gatewayError
                    ?: 'Order status SMS could not be sent. Gateway response: '.$response;
                $this->markCourierSmsFailure($courierShipment, $failureMessage);
            }
        }

        return $isSuccess;
    }

    public function courierOutForDeliveryMessage(Order $order, OrderCourierShipment $shipment): string
    {
        $orderNumber = '#'.$order->id;
        $riderName = trim((string) $shipment->courier_rider_name);
        $riderPhone = trim((string) $shipment->courier_rider_phone);

        if (filled($riderName) && filled($riderPhone)) {
            return "Dear Customer, your order {$orderNumber} is out for delivery. Delivery person: {$riderName}, Mobile: {$riderPhone}. Please keep your phone available. – Ammar";
        }

        if (filled($riderPhone)) {
            return "Dear Customer, your order {$orderNumber} is out for delivery. Delivery person’s mobile: {$riderPhone}. Please keep your phone available. – Ammar";
        }

        return "Dear Customer, your order {$orderNumber} is out for delivery. Please keep your phone available for the courier delivery call. – Ammar";
    }

    private function normalizeStatus(string $status): string
    {
        return match ($status) {
            'delivery_boy_delivered' => 'delivered',
            default => $status,
        };
    }

    private function resolveNotificationKey(string $status): ?string
    {
        return match ($status) {
            'pending' => 'customer_order_placed',
            'confirmed' => 'customer_order_confirmed',
            'processing' => 'customer_order_packaging',
            'handover', 'courier_ready' => 'customer_order_handed_to_courier',
            'picked_up' => 'customer_order_out_for_delivery',
            'delivered' => 'customer_order_delivered',
            'returned' => 'customer_order_returned',
            default => null,
        };
    }

    private function sanitizeMessage(mixed $message): ?string
    {
        if (! filled($message) || $message === 0 || $message === '0') {
            return null;
        }

        $text = html_entity_decode(strip_tags((string) $message), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $text = preg_replace('/\s+/', ' ', trim($text));

        return filled($text) ? $text : null;
    }

    private function resolvePhoneNumber(Order $order): ?string
    {
        $deliveryAddress = json_decode($order->getRawOriginal('delivery_address') ?? '', true);

        $phone = data_get($deliveryAddress, 'contact_person_number')
            ?: data_get($deliveryAddress, 'phone')
            ?: $order?->customer?->phone
            ?: $order?->guest?->phone;

        if (! filled($phone)) {
            return null;
        }

        return str_replace([' ', '-', '(', ')'], '', trim((string) $phone));
    }

    private function hasSuccessfulLog(int $orderId, string $status): bool
    {
        return OrderStatusSmsLog::where('order_id', $orderId)
            ->where('status', $status)
            ->where('is_success', true)
            ->exists();
    }

    private function markCourierSmsFailure(OrderCourierShipment $shipment, string $error): void
    {
        $attempts = (int) $shipment->out_for_delivery_sms_attempts;
        $delays = [5, 15, 30];

        $shipment->fill([
            'out_for_delivery_sms_status' => $attempts >= 3 ? 'exhausted' : 'failed',
            'out_for_delivery_sms_last_error' => $error,
            'out_for_delivery_sms_next_retry_at' => $attempts >= 3
                ? null
                : now()->addMinutes($delays[max(0, $attempts - 1)]),
        ])->save();
    }

    private function storeLog(
        Order $order,
        string $status,
        string $notificationKey,
        ?string $phone,
        string $message,
        ?string $gateway,
        string $response,
        bool $isSuccess,
        ?string $errorMessage
    ): void {
        try {
            OrderStatusSmsLog::create([
                'order_id' => $order->id,
                'status' => $status,
                'notification_key' => $notificationKey,
                'phone' => $phone,
                'message' => $message,
                'gateway' => $gateway,
                'gateway_response' => $response,
                'is_success' => $isSuccess,
                'error_message' => $errorMessage,
                'sent_at' => now(),
            ]);
        } catch (\Throwable $throwable) {
            Log::error('Order status SMS log could not be stored.', [
                'order_id' => $order->id,
                'status' => $status,
                'is_success' => $isSuccess,
                'message' => $throwable->getMessage(),
            ]);
        }
    }
}
