<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\Shipment;
use App\Services\Carriers\CarrierServiceFactory;
use Illuminate\Support\Facades\Log;
use Throwable;

class DelhiveryApiDeliveryStatusSyncing extends Command
{
    protected $signature = 'delhivery:sync-delivery-status';
    protected $description = 'Sync Delhivery shipment tracking and update shipment/order statuses';

    public function handle()
    {
        $counters = [
            'processed' => 0,
            'updated' => 0,
            'skipped' => 0,
            'failed' => 0,
            'orders_updated' => 0,
        ];

        $affectedOrderIds = [];

        try {
            $carrierService = CarrierServiceFactory::make('delhivery');
        } catch (Throwable $e) {
            $this->error('Unable to initialize Delhivery carrier: ' . $e->getMessage());
            Log::error('Delhivery sync bootstrap failed', ['error' => $e->getMessage()]);

            return Command::FAILURE;
        }

        $shipments = Shipment::query()
            ->whereNotNull('tracking_id')
            ->where('status', '!=', Shipment::STATUS_DELIVERED)
            ->whereHas('carrier', function ($query) {
                $query->where('slug', 'delhivery');
            })
            ->get();

        if ($shipments->isEmpty()) {
            $this->info('No pending Delhivery shipments found for syncing.');
            return Command::SUCCESS;
        }

        $shipmentIdsByTracking = $shipments
            ->groupBy('tracking_id')
            ->map(static fn ($group) => $group->pluck('id')->values()->all());

        foreach ($shipmentIdsByTracking as $trackingId => $shipmentIds) {
            $counters['processed']++;

            try {
                $trackingData = $carrierService->track((string) $trackingId);
            } catch (Throwable $e) {
                $counters['failed']++;
                Log::error('Delhivery tracking request failed', [
                    'tracking_id' => $trackingId,
                    'error' => $e->getMessage(),
                ]);
                continue;
            }

            if (!$trackingData || !is_array($trackingData)) {
                $counters['skipped']++;
                Log::warning('Delhivery tracking response missing/invalid', [
                    'tracking_id' => $trackingId,
                ]);
                continue;
            }

            $normalized = $this->normalizeTrackingStatus($trackingData);

            if (empty($normalized['external_status'])) {
                $counters['skipped']++;
                Log::warning('Delhivery tracking status not found', [
                    'tracking_id' => $trackingId,
                    'tracking_data' => $trackingData,
                ]);
                continue;
            }

            $matchingShipments = $shipments->whereIn('id', $shipmentIds);
            if ($matchingShipments->isEmpty()) {
                $counters['skipped']++;
                continue;
            }

            foreach ($matchingShipments as $shipment) {
                $metadata = $shipment->metadata ?? [];
                if (!is_array($metadata)) {
                    $metadata = [];
                }

                $metadata['tracking_sync'] = [
                    'external_status' => $normalized['external_status'],
                    'external_substatus' => $normalized['external_substatus'],
                    'normalized_order_status' => $normalized['order_status'],
                    'normalized_shipment_status' => $normalized['shipment_status'],
                    'history' => $normalized['history'] ?? [],
                    'synced_at' => now()->toDateTimeString(),
                ];

                $newShipmentStatus = $normalized['shipment_status'] ?? Shipment::STATUS_CREATED;
                $shipmentChanged = $shipment->status !== $newShipmentStatus;

                $shipment->update([
                    'status' => $newShipmentStatus,
                    'metadata' => $metadata,
                    'raw_response' => $trackingData,
                ]);

                if ($shipmentChanged) {
                    $counters['updated']++;
                } else {
                    $counters['skipped']++;
                }

                $affectedOrderIds[$shipment->order_id] = true;
            }
        }

        if (!empty($affectedOrderIds)) {
            $orders = Order::query()
                ->whereIn('id', array_keys($affectedOrderIds))
                ->with(['shipments.carrier'])
                ->get();

            foreach ($orders as $order) {
                if ($this->syncOrderStatusFromDelhiveryShipments($order)) {
                    $counters['orders_updated']++;
                }
            }
        }

        Log::info('Delhivery delivery status sync completed', $counters);
        $this->info('Delhivery sync completed: ' . json_encode($counters));

        return Command::SUCCESS;
    }

    private function normalizeTrackingStatus(array $trackingData): array
    {
        $shipmentNode = $trackingData['Shipment'] ?? [];
        $statusNode = $shipmentNode['Status'] ?? [];
        $historyNode = $shipmentNode['Scans'] ?? [];

        $statusText = $statusNode['Status'] ?? null;
        $instructions = $statusNode['Instructions'] ?? null;

        $combined = strtolower(trim(($statusText ?? '') . ' ' . ($instructions ?? '')));
        $combined = str_replace(['-', ' '], ['_', ' '], $combined);

        $isDelivered = $this->containsAny($combined, [
            'delivered',
            'dlvd',
            'delivery complete',
        ]);

        $isCancelled = $this->containsAny($combined, [
            'cancelled',
            'canceled',
            'rto delivered',
            'rto closed',
            'shipment cancelled',
            'undelivered_return',
            'return to origin delivered',
        ]);

        $isShipped = $this->containsAny($combined, [
            'in transit',
            'in_transit',
            'dispatched',
            'out for delivery',
            'out_for_delivery',
            'reached destination',
            'bagged',
            'in network',
            'with delivery executive',
            'picked up',
            'picked_up',
            'in route',
        ]);

        $isAwaitingPickup = $this->containsAny($combined, [
            'shipment created',
            'shipment_created',
            'manifested',
            'not picked',
            'not_picked',
            'pickup pending',
            'awaiting pickup',
            'data_received',
            'booked',
        ]);

        $orderStatus = Order::STATUS_AWAITING_PICKUP;
        if ($isDelivered) {
            $orderStatus = Order::STATUS_DELIVERED;
        } elseif ($isCancelled) {
            $orderStatus = Order::STATUS_CANCELLED;
        } elseif ($isShipped) {
            $orderStatus = Order::STATUS_SHIPPED;
        } elseif ($isAwaitingPickup) {
            $orderStatus = Order::STATUS_AWAITING_PICKUP;
        }

        $shipmentStatus = strtolower(str_replace('_', ' ', (string) ($statusText ?? '')));
        if ($shipmentStatus === '') {
            $shipmentStatus = Shipment::STATUS_CREATED;
        }

        return [
            'external_status' => $statusText,
            'external_substatus' => $instructions,
            'order_status' => $orderStatus,
            'shipment_status' => $shipmentStatus,
            'history' => $historyNode,
        ];
    }

    private function syncOrderStatusFromDelhiveryShipments(Order $order): bool
    {
        $delhiveryShipments = $order->shipments
            ->filter(static fn ($shipment) => optional($shipment->carrier)->slug === 'delhivery');

        if ($delhiveryShipments->isEmpty()) {
            return false;
        }

        $hasDelivered = false;
        $hasCancelled = false;
        $hasShipped = false;
        $hasAwaitingPickup = false;

        foreach ($delhiveryShipments as $shipment) {
            $sync = $shipment->metadata['tracking_sync'] ?? null;
            $normalizedOrderStatus = is_array($sync) ? ($sync['normalized_order_status'] ?? null) : null;

            if ($normalizedOrderStatus === Order::STATUS_DELIVERED) {
                $hasDelivered = true;
            } elseif ($normalizedOrderStatus === Order::STATUS_CANCELLED) {
                $hasCancelled = true;
            } elseif ($normalizedOrderStatus === Order::STATUS_SHIPPED) {
                $hasShipped = true;
            } elseif ($normalizedOrderStatus === Order::STATUS_AWAITING_PICKUP) {
                $hasAwaitingPickup = true;
            }
        }

        $derivedStatus = null;
        if ($hasDelivered) {
            $derivedStatus = Order::STATUS_DELIVERED;
        } elseif ($hasCancelled) {
            $derivedStatus = Order::STATUS_CANCELLED;
        } elseif ($hasShipped) {
            $derivedStatus = Order::STATUS_SHIPPED;
        } elseif ($hasAwaitingPickup) {
            $derivedStatus = Order::STATUS_AWAITING_PICKUP;
        }

        if ($derivedStatus === null) {
            return false;
        }

        $current = $order->status;
        $next = $this->resolveNextOrderStatus($current, $derivedStatus);

        if ($next === $current) {
            return false;
        }

        $order->update(['status' => $next]);
        return true;
    }

    private function resolveNextOrderStatus(string $current, string $derived): string
    {
        // Respect terminal statuses unless tracking explicitly indicates delivery/cancellation.
        if ($derived === Order::STATUS_DELIVERED) {
            return Order::STATUS_DELIVERED;
        }

        if ($derived === Order::STATUS_CANCELLED) {
            return $current === Order::STATUS_DELIVERED ? $current : Order::STATUS_CANCELLED;
        }

        if (in_array($current, [Order::STATUS_DELIVERED, Order::STATUS_CANCELLED], true)) {
            return $current;
        }

        return $derived;
    }

    private function containsAny(string $haystack, array $needles): bool
    {
        foreach ($needles as $needle) {
            if ($needle !== '' && str_contains($haystack, $needle)) {
                return true;
            }
        }

        return false;
    }
}
