<?php

namespace App\Services\Courier;

use App\CentralLogics\OrderLogic;
use App\Models\Order;
use App\Models\OrderCourierShipment;
use App\Models\OrderTransaction;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;

class CourierCodSettlementService
{
    private const COLLECTION_STATUS_KEYS = [
        'cod_collection_status',
        'collection_status',
        'cod_status',
    ];

    private const SETTLEMENT_STATUS_KEYS = [
        'settlement_status',
        'courier_settlement_status',
        'paid_status',
        'payment_status',
        'settlement_paid_status',
    ];

    private const COLLECTION_AMOUNT_KEYS = [
        'collected_amount',
        'cod_collected_amount',
        'collection_amount',
        'paid_amount',
        'cod_amount',
    ];

    private const SETTLEMENT_AMOUNT_KEYS = [
        'settled_amount',
        'settlement_amount',
        'payment_amount',
        'paid_amount',
    ];

    private const COLLECTION_DATE_KEYS = [
        'collection_date',
        'collected_at',
        'cod_collected_at',
        'updated_at',
    ];

    private const SETTLEMENT_DATE_KEYS = [
        'settlement_date',
        'payment_date',
        'settlement_paid_at',
        'paid_at',
        'updated_at',
    ];

    private const SETTLEMENT_REFERENCE_KEYS = [
        'settlement_reference',
        'payment_reference',
        'transaction_id',
        'trx_id',
        'payment_id',
    ];

    public function __construct(
        private readonly CourierLogService $logService
    ) {
    }

    public function hasCodSignal(array $payload): bool
    {
        return filled($this->firstFilled($payload, array_merge(
            self::COLLECTION_STATUS_KEYS,
            self::SETTLEMENT_STATUS_KEYS,
            self::COLLECTION_AMOUNT_KEYS,
            self::SETTLEMENT_AMOUNT_KEYS,
            self::COLLECTION_DATE_KEYS,
            self::SETTLEMENT_DATE_KEYS,
            self::SETTLEMENT_REFERENCE_KEYS,
        ))) || $this->hasPayloadKey($payload, 'is_paid') || $this->hasPayloadKey($payload, 'is_settled');
    }

    public function sync(
        Order $order,
        OrderCourierShipment $shipment,
        array $payload = [],
        ?string $courierStatus = null,
        ?int $adminId = null,
        string $source = 'sync'
    ): bool {
        $shipment->refresh();
        $order->refresh();

        $collectableAmount = $this->amount(
            $this->firstFilled($payload, self::COLLECTION_AMOUNT_KEYS)
        ) ?? (float) ($shipment->collectable_amount ?? 0);

        if (! $this->isCodOrder($order, $shipment, $collectableAmount)) {
            return false;
        }

        $collectionStatus = $this->normalizedStatus(
            $this->firstFilled($payload, self::COLLECTION_STATUS_KEYS) ?? $courierStatus ?? $shipment->courier_status
        );
        $settlementStatus = $this->normalizedStatus(
            $this->firstFilled($payload, self::SETTLEMENT_STATUS_KEYS)
        );

        $collectionConfirmed = $this->isCollectionConfirmed($collectionStatus, $courierStatus, $order);
        $settlementConfirmed = $this->isSettlementConfirmed($settlementStatus, $payload);
        $hasExplicitSettlementSignal = $this->hasExplicitSettlementSignal($payload);

        if ($settlementConfirmed) {
            $collectionConfirmed = true;
        }

        if (! $collectionConfirmed && ! $settlementConfirmed) {
            return false;
        }

        $now = now();
        $collectedAmount = $this->amount(
            $this->firstFilled($payload, self::COLLECTION_AMOUNT_KEYS)
        ) ?? $shipment->cod_collected_amount ?? $collectableAmount;
        $settledAmount = $this->amount(
            $this->firstFilled($payload, self::SETTLEMENT_AMOUNT_KEYS)
        ) ?? $shipment->settled_amount ?? $collectedAmount;
        $collectedAt = $this->date($this->firstFilled($payload, self::COLLECTION_DATE_KEYS)) ?? $shipment->cod_collected_at ?? $now;
        $settledAt = $this->date($this->firstFilled($payload, self::SETTLEMENT_DATE_KEYS)) ?? $shipment->settlement_paid_at ?? $now;
        $settlementReference = $this->firstFilled($payload, self::SETTLEMENT_REFERENCE_KEYS) ?? $shipment->settlement_reference;
        $nextSettlementStatus = $shipment->settlement_status;

        if ($settlementConfirmed) {
            $nextSettlementStatus = $settlementStatus ?: 'settled';
        } elseif (! $shipment->courier_accounting_synced_at && filled($settlementStatus)) {
            $nextSettlementStatus = $settlementStatus;
        }

        $shipment->forceFill([
            'cod_collection_status' => $collectionConfirmed ? 'collected' : ($shipment->cod_collection_status ?? null),
            'cod_collected_amount' => $collectionConfirmed ? $collectedAmount : $shipment->cod_collected_amount,
            'cod_collected_at' => $collectionConfirmed ? $collectedAt : $shipment->cod_collected_at,
            'settlement_status' => $nextSettlementStatus,
            'settled_amount' => $settlementConfirmed ? $settledAmount : $shipment->settled_amount,
            'settlement_paid_at' => $settlementConfirmed ? $settledAt : $shipment->settlement_paid_at,
            'settlement_reference' => $settlementReference,
            'settlement_payload' => $payload ?: $shipment->settlement_payload,
        ])->save();

        $this->logService->write(
            action: $settlementConfirmed ? 'cod_settlement_detected' : 'cod_collection_detected',
            order: $order,
            shipment: $shipment,
            requestPayload: ['source' => $source],
            responsePayload: $payload,
            httpStatus: 200,
            errorMessage: null,
            adminId: $adminId
        );

        if (! $this->shouldSyncAccounting($collectionConfirmed, $settlementConfirmed, $hasExplicitSettlementSignal)) {
            return false;
        }

        if ($shipment->courier_accounting_synced_at) {
            $this->logService->write(
                action: 'cod_accounting_duplicate_skipped',
                order: $order,
                shipment: $shipment,
                requestPayload: ['source' => $source],
                responsePayload: $payload,
                httpStatus: 200,
                errorMessage: null,
                adminId: $adminId
            );

            return false;
        }

        $existingOrderTransaction = OrderTransaction::withoutGlobalScopes()
            ->where('order_id', $order->id)
            ->exists();

        if (! $existingOrderTransaction) {
            $transactionCreated = OrderLogic::create_transaction($order->fresh(['store', 'store.vendor', 'delivery_man', 'customer']), false, null);

            if (! $transactionCreated) {
                $shipment->forceFill([
                    'courier_accounting_note' => 'Courier COD accounting failed while creating the order transaction.',
                ])->save();

                $this->logService->write(
                    action: 'cod_accounting_failed',
                    order: $order,
                    shipment: $shipment,
                    requestPayload: ['source' => $source],
                    responsePayload: $payload,
                    httpStatus: 500,
                    errorMessage: 'Courier COD accounting failed while creating the order transaction.',
                    adminId: $adminId
                );

                Log::error('Courier COD accounting failed while creating order transaction.', [
                    'order_id' => $order->id,
                    'shipment_id' => $shipment->id,
                    'source' => $source,
                ]);

                return false;
            }
        }

        if ($order->payment_status !== 'paid') {
            $order->payment_status = 'paid';
            $order->save();
        }

        OrderLogic::update_unpaid_order_payment(
            order_id: $order->id,
            payment_method: $order->payment_method
        );

        $shipment->forceFill([
            'cod_received_at' => $shipment->cod_received_at ?: ($settlementConfirmed ? $settledAt : $collectedAt),
            'cod_received_by_admin_id' => $shipment->cod_received_by_admin_id ?: $adminId,
            'cod_note' => $shipment->cod_note ?: 'Courier COD accounting synced from '.$source.'.',
            'settlement_status' => $settlementConfirmed ? ($settlementStatus ?: 'settled') : ($shipment->settlement_status ?: 'collection_accounted'),
            'settled_amount' => $settlementConfirmed ? $settledAmount : $shipment->settled_amount,
            'settlement_paid_at' => $settlementConfirmed ? $settledAt : $shipment->settlement_paid_at,
            'courier_accounting_synced_at' => $now,
            'courier_accounting_note' => $existingOrderTransaction
                ? 'Existing order transaction found; courier accounting marked synced without duplicate transaction.'
                : 'Courier COD accounting synced successfully.',
        ])->save();

        $this->logService->write(
            action: $existingOrderTransaction ? 'cod_accounting_existing_transaction' : 'cod_accounting_synced',
            order: $order,
            shipment: $shipment,
            requestPayload: ['source' => $source],
            responsePayload: $payload,
            httpStatus: 200,
            errorMessage: null,
            adminId: $adminId
        );

        return true;
    }

    private function isCodOrder(Order $order, OrderCourierShipment $shipment, float $collectableAmount): bool
    {
        if ($collectableAmount <= 0 && (float) ($shipment->collectable_amount ?? 0) <= 0) {
            return false;
        }

        if ($order->payment_method === 'cash_on_delivery') {
            return true;
        }

        if ($order->payment_method === 'partial_payment') {
            return $order->payments()
                ->where('payment_method', 'cash_on_delivery')
                ->exists();
        }

        return (float) ($shipment->collectable_amount ?? 0) > 0;
    }

    private function isCollectionConfirmed(?string $collectionStatus, ?string $courierStatus, Order $order): bool
    {
        $statuses = array_filter([
            $collectionStatus,
            $this->normalizedStatus($courierStatus),
            $this->normalizedStatus($order->order_status),
        ]);

        foreach ($statuses as $status) {
            if (in_array($status, [
                'delivered',
                'delivered_approval_pending',
                'partial_delivered',
                'partial_delivered_approval_pending',
                'collected',
                'cod_collected',
                'cash_collected',
                'received',
                'paid',
                'settled',
                'completed',
                'complete',
                'success',
                'successful',
            ], true)) {
                return true;
            }
        }

        return false;
    }

    private function isSettlementConfirmed(?string $settlementStatus, array $payload): bool
    {
        if (in_array($settlementStatus, [
            'settled',
            'paid',
            'payment_received',
            'received',
            'remitted',
            'transferred',
            'deposited',
            'completed',
            'complete',
            'success',
            'successful',
            'yes',
            'true',
            '1',
        ], true)) {
            return true;
        }

        foreach (['is_paid', 'is_settled'] as $key) {
            if ($this->hasPayloadKey($payload, $key) && filter_var($this->firstFilled($payload, [$key]), FILTER_VALIDATE_BOOLEAN)) {
                return true;
            }
        }

        return false;
    }

    private function shouldSyncAccounting(bool $collectionConfirmed, bool $settlementConfirmed, bool $hasExplicitSettlementSignal): bool
    {
        if ($settlementConfirmed) {
            return true;
        }

        return $collectionConfirmed && ! $hasExplicitSettlementSignal;
    }

    private function hasExplicitSettlementSignal(array $payload): bool
    {
        foreach (['is_paid', 'is_settled'] as $key) {
            if ($this->hasPayloadKey($payload, $key)) {
                return true;
            }
        }

        foreach (array_merge(
            self::SETTLEMENT_STATUS_KEYS,
            self::SETTLEMENT_AMOUNT_KEYS,
            self::SETTLEMENT_DATE_KEYS,
            self::SETTLEMENT_REFERENCE_KEYS
        ) as $key) {
            if (filled($this->firstFilled($payload, [$key]))) {
                return true;
            }
        }

        return false;
    }

    private function firstFilled(array $payload, array $keys): mixed
    {
        foreach ($keys as $key) {
            $value = data_get($payload, $key);

            if (blank($value) && ! str_contains($key, '.')) {
                $value = $this->findRecursiveValue($payload, $key);
            }

            if (filled($value)) {
                return $value;
            }
        }

        return null;
    }

    private function findRecursiveValue(array $payload, string $key): mixed
    {
        foreach ($payload as $payloadKey => $value) {
            if ($payloadKey === $key && filled($value)) {
                return $value;
            }

            if (is_array($value)) {
                $nestedValue = $this->findRecursiveValue($value, $key);

                if (filled($nestedValue)) {
                    return $nestedValue;
                }
            }
        }

        return null;
    }

    private function hasPayloadKey(array $payload, string $key): bool
    {
        if (Arr::has($payload, $key)) {
            return true;
        }

        if (str_contains($key, '.')) {
            return false;
        }

        foreach ($payload as $payloadKey => $value) {
            if ($payloadKey === $key) {
                return true;
            }

            if (is_array($value) && $this->hasPayloadKey($value, $key)) {
                return true;
            }
        }

        return false;
    }

    private function amount(mixed $value): ?float
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (is_numeric($value)) {
            return (float) $value;
        }

        if (is_string($value)) {
            $normalized = preg_replace('/[^0-9.\-]/', '', $value);

            return is_numeric($normalized) ? (float) $normalized : null;
        }

        return null;
    }

    private function date(mixed $value): ?Carbon
    {
        if (blank($value)) {
            return null;
        }

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

    private function normalizedStatus(mixed $status): ?string
    {
        if (blank($status)) {
            return null;
        }

        return str_replace(['-', ' '], '_', strtolower(trim((string) $status)));
    }
}
