<?php

namespace App\Services\Courier;

use App\Exceptions\CourierException;
use App\Models\CustomerAddress;
use App\Models\Order;
use Illuminate\Support\Str;

class CourierPayloadBuilder
{
    public function build(Order $order, array $senderProfile): array
    {
        if ($order->delivery_man_id) {
            throw new CourierException('এই order-এ delivery man already assigned আছে। Courier-এ পাঠানোর আগে manual assign remove করতে হবে।');
        }

        $address = $this->resolveDeliveryAddress($order);

        $customerName = trim((string) data_get($address, 'contact_person_name'));
        $customerPhone = trim((string) data_get($address, 'contact_person_number'));
        $fullAddress = $this->buildFullAddress($address);

        if (blank($customerName)) {
            $customerName = trim((string) optional($order->customer)->f_name . ' ' . optional($order->customer)->l_name);
        }
        if (blank($customerPhone)) {
            $customerPhone = trim((string) optional($order->customer)->phone);
        }

        if (blank($customerName)) {
            throw new CourierException('Customer name missing থাকায় courier request পাঠানো যাচ্ছে না।');
        }

        if (blank($customerPhone)) {
            throw new CourierException('Customer phone missing থাকায় courier request পাঠানো যাচ্ছে না।');
        }

        if (blank($fullAddress)) {
            throw new CourierException('Full delivery address missing থাকায় courier request পাঠানো যাচ্ছে না।');
        }

        $customerName = $this->limitText($customerName, 100);
        $fullAddress = $this->limitText($fullAddress, 250);

        $collectableAmount = $this->resolveCollectableAmount($order);
        $defaultParcelNote = trim((string) data_get($senderProfile, 'default_parcel_note'));
        $parcelWeightKg = $this->resolveParcelWeightKg($order);

        $itemSummary = $order->details
            ? $order->details->map(fn ($detail) => trim($detail->display_item_name . ' x ' . $detail->quantity))->filter()->implode(', ')
            : '';

        $noteParts = array_filter([
            $defaultParcelNote,
            $parcelWeightKg > 0 ? 'Weight: ' . number_format($parcelWeightKg, 3, '.', '') . ' KG' : null,
            'Order #' . $order->id,
        ]);

        return [
            'invoice'            => (string) $order->id,
            'recipient_name'     => $customerName,
            'recipient_phone'    => $this->normalizePhone($customerPhone),
            'recipient_address'  => $fullAddress,
            'cod_amount'         => $collectableAmount,
            'note'               => implode(' | ', $noteParts),
            'item_description'   => filled($itemSummary) ? $itemSummary : 'N/A',
        ];
    }

    private function resolveDeliveryAddress(Order $order): array
    {
        $attributes = $order->getAttributes();
        $rawAddress = ($attributes['delivery_address'] ?? null)
            ?: ($attributes['shipping_address_data'] ?? null)
            ?: [];
        $snapshot = is_array($rawAddress)
            ? $rawAddress
            : (is_string($rawAddress) ? (json_decode($rawAddress, true) ?: []) : []);

        if (! $order->delivery_address_id) {
            return $snapshot;
        }

        $storedAddress = CustomerAddress::withoutGlobalScopes()
            ->find($order->delivery_address_id)?->toArray() ?? [];

        return array_replace(
            $storedAddress,
            array_filter($snapshot, fn ($value) => $value !== null && $value !== '')
        );
    }

    private function firstAddressValue(array $address, array $keys): string
    {
        foreach ($keys as $key) {
            $value = trim((string) data_get($address, $key));
            if ($value !== '') {
                return $value;
            }
        }

        return '';
    }

    public function resolveCollectableAmount(Order $order): float
    {
        $summary = $order->financialSummary();

        if ($order->payment_status === 'paid' || in_array($order->payment_method, ['wallet'], true)) {
            return $summary['courier_collectable'];
        }

        if (in_array($order->payment_method, ['cash_on_delivery', 'partial_payment'], true)) {
            return $summary['courier_collectable'];
        }

        throw new CourierException('Unpaid digital/offline order courier-এ পাঠানো যাবে না। আগে payment verify করতে হবে।');
    }

    private function buildFullAddress(array $address): string
    {
        $baseAddress = trim((string) data_get($address, 'address'));
        $house       = trim((string) data_get($address, 'house'));
        $road        = trim((string) data_get($address, 'road'));
        $floor       = trim((string) data_get($address, 'floor'));
        $thana       = $this->firstAddressValue($address, ['thana', 'thana_name', 'upazila', 'upazila_name', 'area', 'area_name']);
        $district    = $this->firstAddressValue($address, ['district', 'district_name', 'city', 'city_name']);

        $knownAddress = strtolower(implode(', ', array_filter([$house, $road, $floor, $baseAddress])));

        $prefixParts = array_filter(
            [$house, $road, $floor],
            fn ($part) => filled($part) && ! str_contains(strtolower($baseAddress), strtolower($part))
        );
        $locationParts = [];
        if (filled($thana) && ! str_contains($knownAddress, strtolower($thana))) {
            $locationParts[] = 'Thana: '.$thana;
        }
        if (filled($district) && ! str_contains($knownAddress, strtolower($district))) {
            $locationParts[] = 'District: '.$district;
        }

        $parts = array_filter([implode(', ', $prefixParts), $baseAddress, ...$locationParts]);

        return implode(', ', $parts);
    }

    private function limitText(string $value, int $limit): string
    {
        $value = preg_replace('/\s+/u', ' ', trim($value)) ?? trim($value);

        return Str::limit($value, $limit, '');
    }

    private function normalizePhone(string $phone): string
    {
        $digits = preg_replace('/\D+/', '', $phone);

        if (blank($digits)) {
            return '';
        }

        if (str_starts_with($digits, '880') && strlen($digits) >= 13) {
            $digits = substr($digits, 3);
        } elseif (str_starts_with($digits, '88') && strlen($digits) >= 12) {
            $digits = substr($digits, 2);
        }

        if (str_starts_with($digits, '0') && strlen($digits) > 11) {
            $digits = substr($digits, -11);
        }

        if (!str_starts_with($digits, '0') && strlen($digits) === 10 && str_starts_with($digits, '1')) {
            $digits = '0' . $digits;
        }

        if (!preg_match('/^01\d{9}$/', $digits)) {
            throw new CourierException('Courier phone number অবশ্যই 01975553300 format-এর ১১ digit Bangladesh local number হতে হবে।');
        }

        return $digits;
    }

    private function resolveParcelWeightKg(Order $order): float
    {
        $details = $order->details ?? collect();
        $totalWeightKg = 0.0;

        foreach ($details as $detail) {
            $itemWeightKg = (float) ($detail->item?->weight_kg ?? 0);

            if ($itemWeightKg <= 0) {
                continue;
            }

            $quantity = max((int) ($detail->quantity ?? 1), 1);
            $totalWeightKg += ($itemWeightKg * $quantity);
        }

        return round($totalWeightKg, 3);
    }
}
