<?php

namespace App\Services\Courier;

use App\CentralLogics\Helpers;
use App\Models\CourierStatusEvent;
use App\Models\Order;
use App\Models\OrderCourierShipment;
use App\Services\OrderStatusSmsService;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class CourierStatusProcessor
{
    public function __construct(
        private readonly CourierStatusMapper $statusMapper,
        private readonly CourierEventMetadataExtractor $metadataExtractor,
        private readonly OrderStatusSmsService $smsService
    ) {
    }

    public function process(
        Order $order,
        OrderCourierShipment $shipment,
        array $payload,
        string $source,
        ?int $adminId = null,
        ?string $fallbackStatus = null
    ): array {
        $provider = $shipment->provider ?: 'steadfast';
        $metadata = $this->metadataExtractor->extract($payload, $fallbackStatus);
        $mapping = $this->statusMapper->map($metadata['status_code'], $provider);
        $riderRequired = (bool) ($mapping['requires_rider'] ?? false);
        $riderConfirmed = (bool) $metadata['has_rider'];

        if ($riderRequired && ! $riderConfirmed) {
            $mapping['should_update_order'] = false;
            $mapping['mapping_result'] = 'out_for_delivery_rejected_missing_rider';
        }

        $eventTime = $this->parseDate($metadata['event_time']) ?? now();
        $firstOutForDeliveryConfirmation = $mapping['mapping_result'] === 'out_for_delivery'
            && $shipment->out_for_delivery_at === null;

        $shipment->fill([
            'courier_status' => $metadata['status_code'] ?: $shipment->courier_status,
            'courier_status_code' => $metadata['status_code'] ?: $shipment->courier_status_code,
            'courier_status_text' => $metadata['status_text'] ?: $shipment->courier_status_text,
            'courier_rider_id' => $metadata['rider_id'] ?: $shipment->courier_rider_id,
            'courier_rider_name' => $metadata['rider_name'] ?: $shipment->courier_rider_name,
            'courier_rider_phone' => $metadata['rider_phone'] ?: $shipment->courier_rider_phone,
            'courier_event_reference' => $metadata['event_reference'] ?: $shipment->courier_event_reference,
            'status_mapping_result' => $mapping['mapping_result'],
            'courier_synced_at' => now(),
            'last_synced_by_admin_id' => $adminId ?: $shipment->last_synced_by_admin_id,
        ]);

        if ($mapping['mapping_result'] === 'out_for_delivery') {
            $shipment->out_for_delivery_at = $shipment->out_for_delivery_at ?: $eventTime;
        }

        $shipment->save();
        $legacyStatusCorrected = $this->restoreUnconfirmedCourierOutForDelivery($order, $shipment);

        $eventKey = $this->eventKey($shipment, $metadata);
        $event = CourierStatusEvent::firstOrCreate(
            ['idempotency_key' => $eventKey],
            [
                'order_id' => $order->id,
                'order_courier_shipment_id' => $shipment->id,
                'provider' => $provider,
                'source' => $source,
                'courier_status_code' => $metadata['status_code'],
                'courier_status_text' => $metadata['status_text'],
                'event_reference' => $metadata['event_reference'],
                'rider_id' => $metadata['rider_id'],
                'rider_name' => $metadata['rider_name'],
                'rider_phone' => $metadata['rider_phone'],
                'rider_confirmed' => $riderConfirmed,
                'mapping_result' => $mapping['mapping_result'],
                'accepted' => (bool) ($mapping['should_update_order'] ?? false),
                'event_occurred_at' => $eventTime,
                'processed_at' => now(),
            ]
        );

        if (! $event->wasRecentlyCreated) {
            return [
                'mapping' => $mapping,
                'metadata' => $metadata,
                'duplicate' => true,
                'legacy_status_corrected' => $legacyStatusCorrected,
            ];
        }

        $statusChanged = false;
        if (($mapping['should_update_order'] ?? false) && filled($mapping['internal_status'])) {
            $statusChanged = $this->applyInternalStatus($order, $mapping['internal_status']);
        }

        if ($mapping['mapping_result'] === 'out_for_delivery'
            && ! in_array($order->order_status, ['delivered', 'returned', 'canceled', 'failed'], true)
            && ($statusChanged || $firstOutForDeliveryConfirmation)
        ) {
            $this->notifyAfterCommit($order->id, true);
        } elseif ($statusChanged) {
            $this->notifyAfterCommit($order->id, false);
        }

        return [
            'mapping' => $mapping,
            'metadata' => $metadata,
            'duplicate' => false,
            'legacy_status_corrected' => $legacyStatusCorrected,
        ];
    }

    private function restoreUnconfirmedCourierOutForDelivery(
        Order $order,
        OrderCourierShipment $shipment
    ): bool {
        if ($order->order_status !== 'picked_up'
            || $shipment->isCourierConfirmedOutForDelivery()
        ) {
            return false;
        }

        $order->order_status = 'handover';

        if (Schema::hasColumn($order->getTable(), 'handover')) {
            $order->handover = $order->handover ?: $shipment->sent_to_courier_at ?: now();
        }

        if (Schema::hasColumn($order->getTable(), 'picked_up')) {
            $order->picked_up = null;
        }

        $order->save();

        Log::warning('Corrected unconfirmed courier Out for Delivery status.', [
            'order_id' => $order->id,
            'shipment_id' => $shipment->id,
            'provider' => $shipment->provider,
            'courier_status_code' => $shipment->courier_status_code,
            'status_mapping_result' => $shipment->status_mapping_result,
        ]);

        return true;
    }

    private function applyInternalStatus(Order $order, string $nextStatus): bool
    {
        $currentStatus = $order->order_status;

        if ($currentStatus === $nextStatus || in_array($currentStatus, ['delivered', 'returned'], true)) {
            return false;
        }

        if (in_array($nextStatus, ['returned', 'canceled'], true)) {
            $order->order_status = $nextStatus;
            if (Schema::hasColumn($order->getTable(), $nextStatus)) {
                $order[$nextStatus] = $order[$nextStatus] ?: now();
            }
            $order->save();

            return true;
        }

        $weights = [
            'pending' => 1,
            'accepted' => 2,
            'confirmed' => 2,
            'processing' => 3,
            'handover' => 4,
            'picked_up' => 5,
            'delivered' => 6,
        ];

        if (($weights[$nextStatus] ?? 0) < ($weights[$currentStatus] ?? 0)) {
            return false;
        }

        $order->order_status = $nextStatus;
        if (Schema::hasColumn($order->getTable(), $nextStatus)) {
            $order[$nextStatus] = $order[$nextStatus] ?: now();
        }
        $order->save();

        return true;
    }

    private function notifyAfterCommit(int $orderId, bool $courierOutForDelivery): void
    {
        DB::afterCommit(function () use ($orderId, $courierOutForDelivery) {
            try {
                $order = Order::query()->with('courierShipment')->find($orderId);

                if (! $order) {
                    return;
                }

                if ($courierOutForDelivery && (
                    $order->order_status !== 'picked_up'
                    || ! $order->courierShipment?->isCourierConfirmedOutForDelivery()
                )) {
                    return;
                }

                Helpers::send_order_notification($order);

                if ($courierOutForDelivery) {
                    // The helper may skip SMS when its generic message template is
                    // empty. This explicit call guarantees the required English
                    // courier SMS; the SMS service itself enforces send-once.
                    $this->smsService->sendForOrder($order, 'picked_up', 'courier_out_for_delivery');
                }
            } catch (\Throwable $throwable) {
                Log::error('Courier status notification failed after status commit.', [
                    'order_id' => $orderId,
                    'courier_out_for_delivery' => $courierOutForDelivery,
                    'message' => $throwable->getMessage(),
                ]);
            }
        });
    }

    private function eventKey(OrderCourierShipment $shipment, array $metadata): string
    {
        return hash('sha256', implode('|', [
            strtolower((string) $shipment->provider),
            (string) $shipment->id,
            strtolower((string) $metadata['status_code']),
            (string) $metadata['event_reference'],
            (string) $metadata['rider_id'],
            strtolower((string) $metadata['rider_name']),
            (string) $metadata['rider_phone'],
        ]));
    }

    private function parseDate(?string $value): ?Carbon
    {
        if (! filled($value)) {
            return null;
        }

        try {
            return Carbon::parse($value);
        } catch (\Throwable) {
            return null;
        }
    }
}
