<?php

namespace App\Services\Courier;

use App\Exceptions\CourierException;
use App\Models\Order;
use App\Models\OrderCourierShipment;
use Illuminate\Support\Facades\DB;

class CourierTrackingService
{
    public function __construct(
        private readonly CourierSettingsService $settingsService,
        private readonly CourierProviderManager $providerManager,
        private readonly CourierStatusProcessor $statusProcessor,
        private readonly CourierLogService $logService,
        private readonly CourierCostResolver $courierCostResolver,
        private readonly CourierCodSettlementService $codSettlementService,
        private readonly CourierLedgerService $courierLedgerService
    ) {
    }

    public function sync(Order $order, ?int $adminId = null)
    {
        $result = DB::transaction(function () use ($order, $adminId) {
            $order = Order::with('courierShipment')->lockForUpdate()->findOrFail($order->id);
            $shipment = $order->courierShipment;

            if (! $shipment || ! $shipment->hasSuccessfulBooking()) {
                throw new CourierException('এই order এখনো courier-এ successfully পাঠানো হয়নি।');
            }

            if (! $this->settingsService->isIntegrationEnabled()) {
                throw new CourierException('Courier integration disabled থাকলে sync করা যাবে না।');
            }

            $provider = $this->providerManager->driver($shipment->provider);
            $providerConfig = $this->settingsService->getProviderConfig($shipment->provider);
            $trackingRequest = [
                'consignment_id' => $shipment->courier_consignment_id ?: $shipment->courier_parcel_id,
                'tracking_code' => $shipment->courier_tracking_id,
                'invoice' => (string) $order->id,
            ];
            $trackingResponse = $provider->fetchTrackingStatus($trackingRequest, $providerConfig);
            $maskedResponse = $trackingResponse['masked_raw'] ?? [];
            $httpStatus = $trackingResponse['http_status'] ?? null;
            $errorMessage = ($trackingResponse['success'] ?? false)
                ? null
                : ($trackingResponse['message'] ?? 'Courier sync failed.');

            if (! ($trackingResponse['success'] ?? false)) {
                $shipment->fill([
                    'response_payload' => $maskedResponse ?: null,
                    'last_http_status' => $httpStatus,
                    'last_error_message' => $errorMessage,
                    'courier_synced_at' => now(),
                    'last_synced_by_admin_id' => $adminId,
                ])->save();

                $this->logService->write(
                    action: 'sync',
                    order: $order,
                    shipment: $shipment,
                    requestPayload: $trackingRequest,
                    responsePayload: $maskedResponse,
                    httpStatus: $httpStatus,
                    errorMessage: $errorMessage,
                    adminId: $adminId
                );

                return ['error' => $errorMessage];
            }

            $costData = $this->courierCostResolver->extract($trackingResponse['raw'] ?? []);

            $shipment->fill([
                'courier_status' => $trackingResponse['courier_status'],
                'response_payload' => $maskedResponse ?: null,
                'last_http_status' => $httpStatus,
                'last_error_message' => null,
                'actual_delivery_cost' => $costData['actual_delivery_cost'] ?? $shipment->actual_delivery_cost,
                'actual_return_cost' => $costData['actual_return_cost'] ?? $shipment->actual_return_cost,
                'actual_cod_charge' => $costData['actual_cod_charge'] ?? $shipment->actual_cod_charge,
                'courier_synced_at' => now(),
                'last_synced_by_admin_id' => $adminId,
            ])->save();

            $this->logService->write(
                action: 'sync',
                order: $order,
                shipment: $shipment,
                requestPayload: $trackingRequest,
                responsePayload: $maskedResponse,
                httpStatus: $httpStatus,
                errorMessage: null,
                adminId: $adminId
            );

            $this->statusProcessor->process(
                order: $order,
                shipment: $shipment,
                payload: $trackingResponse['raw'] ?? [],
                source: 'manual_sync',
                adminId: $adminId,
                fallbackStatus: $trackingResponse['courier_status'] ?? null
            );

            return ['shipment' => $shipment->fresh(['logs', 'sentByAdmin', 'lastSyncedByAdmin'])];
        });

        if (isset($result['error'])) {
            throw new CourierException($result['error']);
        }

        return $result['shipment'];
    }

    public function syncFromWebhook(array $payload, string $provider = 'steadfast')
    {
        return DB::transaction(function () use ($payload, $provider) {
            $shipmentId = $this->findShipmentIdFromWebhookPayload($payload, $provider);

            if (! $shipmentId) {
                throw new CourierException('No matching courier shipment found for this SteadFast webhook payload.');
            }

            $shipment = OrderCourierShipment::query()->lockForUpdate()->findOrFail($shipmentId);
            $order = Order::query()->lockForUpdate()->findOrFail($shipment->order_id);

            $courierStatus = trim((string) (data_get($payload, 'status') ?? data_get($payload, 'delivery_status') ?? ''));
            if (blank($courierStatus) && ! $this->codSettlementService->hasCodSignal($payload)) {
                throw new CourierException('Webhook payload missing courier status.');
            }

            $costData = $this->courierCostResolver->extract($payload);

            $courierConsignmentId = trim((string) (data_get($payload, 'consignment_id') ?? ''));
            $courierTrackingId = trim((string) (data_get($payload, 'tracking_code') ?? data_get($payload, 'tracking_id') ?? ''));

            $shipment->fill([
                'provider' => $shipment->provider ?: $provider,
                'booking_status' => $shipment->booking_status === 'not_sent' && filled($courierConsignmentId) ? 'booked' : $shipment->booking_status,
                'courier_status' => filled($courierStatus) ? $courierStatus : $shipment->courier_status,
                'courier_parcel_id' => $shipment->courier_parcel_id ?: ($courierConsignmentId ?: null),
                'courier_tracking_id' => $shipment->courier_tracking_id ?: ($courierTrackingId ?: null),
                'courier_consignment_id' => $shipment->courier_consignment_id ?: ($courierConsignmentId ?: null),
                'collectable_amount' => data_get($payload, 'cod_amount', $shipment->collectable_amount),
                'actual_delivery_cost' => $costData['actual_delivery_cost'] ?? $shipment->actual_delivery_cost,
                'actual_return_cost' => $costData['actual_return_cost'] ?? $shipment->actual_return_cost,
                'actual_cod_charge' => $costData['actual_cod_charge'] ?? $shipment->actual_cod_charge,
                'response_payload' => $payload,
                'last_http_status' => 200,
                'last_error_message' => null,
                'courier_synced_at' => now(),
            ])->save();

            $this->logService->write(
                action: 'webhook',
                order: $order,
                shipment: $shipment,
                requestPayload: $payload,
                responsePayload: $payload,
                httpStatus: 200,
                errorMessage: null,
                adminId: null
            );

            if (filled($courierStatus)) {
                $this->statusProcessor->process(
                    order: $order,
                    shipment: $shipment,
                    payload: $payload,
                    source: 'webhook',
                    fallbackStatus: $courierStatus
                );
            }

            $this->codSettlementService->sync(
                order: $order,
                shipment: $shipment,
                payload: $payload,
                courierStatus: $courierStatus ?: $shipment->courier_status,
                adminId: null,
                source: 'webhook'
            );

            $this->courierLedgerService->recordShipmentAccounting(
                order: $order,
                shipment: $shipment,
                payload: $payload,
                adminId: null,
                source: 'webhook'
            );

            return $shipment->fresh(['logs', 'sentByAdmin', 'lastSyncedByAdmin']);
        });
    }

    public function recordTrackingWebhook(array $payload, string $provider = 'steadfast')
    {
        return DB::transaction(function () use ($payload, $provider) {
            $shipmentId = $this->findShipmentIdFromWebhookPayload($payload, $provider);

            if (! $shipmentId) {
                throw new CourierException('Invalid consignment ID.');
            }

            $shipment = OrderCourierShipment::query()->lockForUpdate()->findOrFail($shipmentId);
            $order = Order::query()->lockForUpdate()->findOrFail($shipment->order_id);

            $courierConsignmentId = trim((string) (data_get($payload, 'consignment_id') ?? ''));
            $courierTrackingId = trim((string) (data_get($payload, 'tracking_code') ?? data_get($payload, 'tracking_id') ?? ''));

            $shipment->fill([
                'provider' => $shipment->provider ?: $provider,
                'courier_parcel_id' => $shipment->courier_parcel_id ?: ($courierConsignmentId ?: null),
                'courier_tracking_id' => $shipment->courier_tracking_id ?: ($courierTrackingId ?: null),
                'courier_consignment_id' => $shipment->courier_consignment_id ?: ($courierConsignmentId ?: null),
                'response_payload' => $payload,
                'last_http_status' => 200,
                'last_error_message' => null,
                'courier_synced_at' => now(),
            ])->save();

            $this->logService->write(
                action: 'tracking_webhook',
                order: $order,
                shipment: $shipment,
                requestPayload: $payload,
                responsePayload: $payload,
                httpStatus: 200,
                errorMessage: null,
                adminId: null
            );

            $trackingStatus = trim((string) (data_get($payload, 'status') ?? data_get($payload, 'delivery_status') ?? ''));
            if (filled($trackingStatus)) {
                $this->statusProcessor->process(
                    order: $order,
                    shipment: $shipment,
                    payload: $payload,
                    source: 'tracking_webhook',
                    fallbackStatus: $trackingStatus
                );
            }

            $this->codSettlementService->sync(
                order: $order,
                shipment: $shipment,
                payload: $payload,
                courierStatus: $shipment->courier_status,
                adminId: null,
                source: 'tracking_webhook'
            );

            $this->courierLedgerService->recordShipmentAccounting(
                order: $order,
                shipment: $shipment,
                payload: $payload,
                adminId: null,
                source: 'tracking_webhook'
            );

            return $shipment->fresh(['logs', 'sentByAdmin', 'lastSyncedByAdmin']);
        });
    }

    private function findShipmentIdFromWebhookPayload(array $payload, string $provider): ?int
    {
        $consignmentId = trim((string) (data_get($payload, 'consignment_id') ?? ''));
        $trackingCode = trim((string) (data_get($payload, 'tracking_code') ?? data_get($payload, 'tracking_id') ?? ''));
        $invoice = trim((string) (data_get($payload, 'invoice') ?? ''));

        if (blank($consignmentId) && blank($trackingCode) && blank($invoice)) {
            return null;
        }

        $query = OrderCourierShipment::query()->where('provider', $provider);

        $query->where(function ($shipmentQuery) use ($consignmentId, $trackingCode, $invoice) {
            if (filled($consignmentId)) {
                $shipmentQuery->orWhere('courier_consignment_id', $consignmentId)
                    ->orWhere('courier_parcel_id', $consignmentId);
            }

            if (filled($trackingCode)) {
                $shipmentQuery->orWhere('courier_tracking_id', $trackingCode);
            }

            if (filled($invoice)) {
                if (is_numeric($invoice)) {
                    $shipmentQuery->orWhere('order_id', (int) $invoice);
                }

                $shipmentQuery->orWhere('request_payload->invoice', $invoice);
            }
        });

        return $query->latest('id')->value('id');
    }

}
