<?php

namespace Opencart\Extension\Alsendo\OrderDetails;

require_once(DIR_EXTENSION . 'alsendo/src/Enums/AddressType.php');
require_once(DIR_EXTENSION . 'alsendo/src/Providers/AdditionalSettingsDataProvider.php');
require_once(DIR_EXTENSION . 'alsendo/src/Providers/SenderAddressDataProvider.php');
require_once(DIR_EXTENSION . 'alsendo/src/Providers/ShippingSettingsDataProvider.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/AdditionalSettingsRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/SenderAddressRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/ShippingMethodRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/ServicesRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Repositories/ShippingSettingsRepository.php');
require_once(DIR_EXTENSION . 'alsendo/src/Services/PackageContentTags.php');
require_once(DIR_EXTENSION . 'alsendo/src/DTO/Entity/AlsendoRegionEntity.php');
require_once(DIR_EXTENSION . 'alsendo/src/DTO/Entity/OrderPickupPointEntity.php');

use Opencart\Extension\Alsendo\DTO\Entity\AlsendoRegionEntity;
use Opencart\Extension\Alsendo\Enums\AddressType;
use Opencart\Extension\Alsendo\Models\ShippingMethod;
use Opencart\Extension\Alsendo\Providers\AdditionalSettingsDataProvider;
use Opencart\Extension\Alsendo\Providers\SenderAddressDataProvider;
use Opencart\Extension\Alsendo\Providers\ShippingSettingsDataProvider;
use Opencart\Extension\Alsendo\Repositories\AdditionalSettingsRepository;
use Opencart\Extension\Alsendo\Repositories\SenderAddressRepository;
use Opencart\Extension\Alsendo\Repositories\ShippingMethodRepository;
use Opencart\Extension\Alsendo\Repositories\ServicesRepository;
use Opencart\Extension\Alsendo\Repositories\ShippingSettingsRepository;
use Opencart\Extension\Alsendo\Services\PackageContentTags;
use Opencart\System\Engine\Registry;

class OrderDetailsPayloadBuilder
{
    private SenderAddressDataProvider $senderAddressDataProvider;
    private ShippingSettingsDataProvider $shippingSettingsDataProvider;
    private ShippingMethodRepository $shippingMethodRepository;
    private ServicesRepository $servicesRepository;
    private AdditionalSettingsDataProvider $additionalSettingsDataProvider;
    private PackageContentTags $packageContentTags;
    private object $db;
    private string $tablePrefix;
    private Registry $registry;
    private AlsendoRegionEntity $currentRegion;

    public function __construct(Registry $registry)
    {
        $this->registry = $registry;
        $this->db = $registry->get('db');
        $this->tablePrefix = defined('DB_PREFIX') ? DB_PREFIX : '';
        $this->senderAddressDataProvider = new SenderAddressDataProvider(new SenderAddressRepository($this->db, $this->tablePrefix));
        $this->shippingSettingsDataProvider = new ShippingSettingsDataProvider(new ShippingSettingsRepository($this->db, $this->tablePrefix));
        $this->shippingMethodRepository = new ShippingMethodRepository($registry, $this->db, $this->tablePrefix);
        $this->servicesRepository = new ServicesRepository($this->db, $this->tablePrefix);
        $this->additionalSettingsDataProvider = new AdditionalSettingsDataProvider(new AdditionalSettingsRepository($this->db, $this->tablePrefix));
        $this->packageContentTags = new PackageContentTags($registry);
        $this->registry->load->model('extension/alsendo/module/alsendo_region');
        /** @var AlsendoRegionEntity $currentRegion */
        $this->currentRegion = $this->registry->model_extension_alsendo_module_alsendo_region->getCurrentRegion();
    }

    public function buildSenderData(): array
    {
        $senderTemplate = $this->senderAddressDataProvider->getData()['sender_address_templates'] ?? [];
        $mainTemplate = null;
        foreach ($senderTemplate as $template) {
            if (!empty($template['main'])) {
                $mainTemplate = $template;
                break;
            }
        }
        if (!$mainTemplate) {
            return [
                'address_type' => 'company',
                'company_name' => '',
                'full_name' => '',
                'street' => '',
                'house_number' => '',
                'apartment_number' => '',
                'block' => '',
                'entrance' => '',
                'floor' => '',
                'country' => '',
                'postal_code' => '',
                'city' => '',
                'phone_number' => '',
                'email' => '',
                'shipping_point' => null,
                'bank_account_number' => '',
                'bank_code' => null,
                'additional_bank_account_number' => null,
                'external_id' => null
            ];
        }
        return [
            'external_id' => $mainTemplate['external_id'] ?? null,
            'sender_template_id' => $mainTemplate['id'] ?? null,
            'address_type' => $mainTemplate['address_type'] ?? 'company',
            'company_name' => $mainTemplate['company_name'] ?? '',
            'full_name' => !empty($mainTemplate['full_name']) ? $mainTemplate['full_name'] : (($mainTemplate['first_name'] ?? '') . ' ' . ($mainTemplate['last_name'] ?? '')),
            'street' => $mainTemplate['street'] ?? '',
            'house_number' => $mainTemplate['building_number'] ?? '',
            'apartment_number' => $mainTemplate['apartment_number'] ?? '',
            'block' => $mainTemplate['block'] ?? null,
            'entrance' => $mainTemplate['entrance'] ?? null,
            'floor' => $mainTemplate['floor'] ?? null,
            'country' => $mainTemplate['country'] ?? '',
            'postal_code' => $mainTemplate['postal_code'] ?? '',
            'city' => $mainTemplate['city'] ?? '',
            'phone_number' => $mainTemplate['phone_number'] ?? '',
            'email' => $mainTemplate['email'] ?? '',
            'shipping_point' => !empty($mainTemplate['pickup_point']) ? json_decode($mainTemplate['pickup_point'], true) : null,
            'bank_account_number' => $mainTemplate['bank_account_number'] ?? '',
            'bank_code' => $mainTemplate['bank_code'] ?? null,
            'additional_bank_account_number' => $mainTemplate['additional_bank_account_number'] ?? null
        ];
    }

    public function buildShippingAddressData(array $order, ?AlsendoRegionEntity $currentRegion = null): array
    {
        $phoneNumber = $order['telephone'] ?? null;
        if (!$phoneNumber && !empty($order['customer_id'])) {
            $customer = $this->getCustomer((int)$order['customer_id']);
            $phoneNumber = $customer['telephone'] ?? '';
        }
        $address1 = trim($order['shipping_address_1'] ?? '');
        $address2 = trim($order['shipping_address_2'] ?? '');
        $base = [
            'address_type' => empty($order['shipping_company']) ? AddressType::HOME : AddressType::COMPANY,
            'company_name' => empty($order['shipping_company']) ? null : $order['shipping_company'],
            'full_name' => ($order['shipping_firstname'] ?? '') . ' ' . ($order['shipping_lastname'] ?? ''),
            'country' => $order['shipping_country'] ?? '',
            'postal_code' => $order['shipping_postcode'] ?? '',
            'city' => $order['shipping_city'] ?? '',
            'phone_number' => $phoneNumber,
            'email' => $order['email'] ?? '',
            'delivery_point' => null
        ];
        if ($currentRegion && strtolower($currentRegion->code) === 'romania') {
            $parsed = $this->parseRomanianAddress($address1, $address2);
            return array_merge($base, $parsed);
        }
        return array_merge($base, [
            'street' => $address1,
            'house_number' => '',
            'apartment_number' => $address2
        ]);
    }

    public function buildPackageData(array $order, bool $isCatalog, ?AlsendoRegionEntity $currentRegion = null): array
    {
        $shippingSettingsData = $this->shippingSettingsDataProvider->getData();
        $mainTemplate = null;
        foreach ($shippingSettingsData['shipping_settings_templates'] ?? [] as $template) {
            if (!empty($template['main'])) {
                $mainTemplate = $template;
                break;
            }
        }
        $shipViaPickupPoint = false;
        $pickupRequest = false;
        if ($currentRegion && strtolower($currentRegion->code) === 'czechia') {
            $shippingMethod = $this->getShippingMethodFromOrder($order);
            if ($shippingMethod) {
                $shipViaPickupPoint = (bool)$shippingMethod->getShippingViaPickupPoint();
                $pickupRequest = (bool)$shippingMethod->getPickupRequest();
            }
        }
        if (!$mainTemplate) {
            return [
                'packages' => [
                    [
                        'package_template_id' => null,
                        'package_width' => null,
                        'package_length' => null,
                        'package_height' => null,
                        'package_weight' => null,
                        'package_shipment_packaging' => null,
                        'template_name' => null,
                        'non_standard' => false,
                    ]
                ],
                'template_id' => null,
                'template_name' => null,
                'package_content' => '',
                'cod_value' => null,
                'declared_value' => null,
                'pickup_type' => null,
                'ship_via_pickup_point' => $shipViaPickupPoint,
                'pickup_request' => $pickupRequest,
                'pickup_request_date' => null
            ];
        }
        $isPossiblyCod = $this->determineIsPossibleCod($order);
        $totalInCurrency = $this->getTotalInCurrency($order);
        $nonStandard = (bool)($mainTemplate['non_standard'] ?? false);
        return [
            'packages' => [
                [
                    'package_template_id' => $mainTemplate['id'] ?? null,
                    'package_width' => $mainTemplate['width'] ?? null,
                    'package_length' => $mainTemplate['length'] ?? null,
                    'package_height' => $mainTemplate['height'] ?? null,
                    'package_weight' => $mainTemplate['weight'] ?? null,
                    'package_shipment_packaging' => $mainTemplate['package_type'] ?? null,
                    'template_name' => $mainTemplate['name'] ?? null,
                    'non_standard' => $nonStandard,
                ]
            ],
            'template_id' => $mainTemplate['id'] ?? null,
            'template_name' => $mainTemplate['name'] ?? null,
            'package_content' => $this->packageContentTags->generatePackageContent($order, $mainTemplate, $isCatalog),
            'cod_value' => $isPossiblyCod ? $totalInCurrency : ($mainTemplate['cash_on_delivery'] ?? null),
            'declared_value' => $this->calculateDeclaredValue($isPossiblyCod, $totalInCurrency ?? 0, (float)($mainTemplate['declared_value'] ?? 0)),
            'pickup_type' => $mainTemplate['pickup_type'] ?? null,
            'ship_via_pickup_point' => $shipViaPickupPoint,
            'pickup_request' => $pickupRequest,
            'pickup_request_date' => null
        ];
    }

    public function buildShipmentData(array $order): array
    {
        $shippingMethodId = null;
        $shippingMethodName = null;
        $serviceId = null;
        $externalServiceId = null;
        $shippingServiceName = null;
        $mapOperator = null;
        $shipmentPickupPoint = null;
        $shipmentPickupPointDisplay = null;

        $additionalSettingsData = $this->additionalSettingsDataProvider->getData();

        if (!empty($order['shipping_method'])) {
            $shippingMethodData = json_decode($order['shipping_method'], true);
            if ($shippingMethodData && !empty($shippingMethodData['code'])) {
                $parts = explode(':', $shippingMethodData['code']);
                $serviceId = end($parts);
                $shippingMethodId = prev($parts);
                if (isset($shippingMethodData['name'])) {
                    $shippingMethod = $this->shippingMethodRepository->getShippingMethodById((int)$shippingMethodId);
                    if ($shippingMethod) {
                        $shippingMethodId = $shippingMethod->getId();
                        $shippingMethodName = $shippingMethod->getName();
                        $serviceId = $shippingMethod->getServiceId();
                        if ($serviceId) {
                            $service = $this->servicesRepository->getServiceById($serviceId);

                            if ($service) {
                                $externalServiceId = $service->getExternalId();
                                $shippingServiceName = $service->getName();
                                $mapOperator = $service->getMapOperatorName();

                                if ($mapOperator) {
                                    if (!empty($additionalSettingsData['additional_settings'])) {
                                        foreach ($additionalSettingsData['additional_settings'] as $key => $additionalSetting) {
                                            if (empty($additionalSetting)) {
                                                continue;
                                            }
                                            if (str_starts_with((string)$key, $mapOperator . '_')) {
                                                $shipmentPickupPoint = $additionalSetting;
                                                $shipmentPickupPointDisplay = isset($additionalSetting['code'], $additionalSetting['name'])
                                                    ? ($additionalSetting['code'] . ' - ' . $additionalSetting['name']) : '';
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }


        $hourFrom = $additionalSettingsData['additional_settings']['pickup_hour_from'] ?? null;
        $hourTo = $additionalSettingsData['additional_settings']['pickup_hour_to'] ?? null;

        return [
            'shipping_method_id' => $shippingMethodId,
            'shipping_method_name' => $shippingMethodName,
            'shipping_service_name' => $shippingServiceName,
            'map_operator' => $mapOperator,
            'shipment_pickup_point_display' => $shipmentPickupPointDisplay,
            'shipment_pickup_point' => $shipmentPickupPoint,
            'service_id' => $serviceId,
            'external_service_id' => $externalServiceId,
            'preferred_pickup_date' => $this->buildPreferredPickupDate(),
            'preferred_pickup_hour_from' => $hourFrom,
            'preferred_pickup_hour_to' => $hourTo,
        ];
    }

    private function buildPreferredPickupDate(): string
    {
        $additionalSettingsData = $this->additionalSettingsDataProvider->getData();
        $additionalSettings = $additionalSettingsData['additional_settings'] ?? [];
        $samedayDispatch = is_array($additionalSettings) && !empty($additionalSettings['sameday_dispatch']);

        return $samedayDispatch
            ? date('Y-m-d')
            : date('Y-m-d', strtotime('next weekday'));
    }

    private function getOrder(int $orderId): ?array
    {
        $query = $this->db->query("SELECT * FROM `" . $this->tablePrefix . "order` WHERE `order_id` = '" . (int)$orderId . "'");
        return $query->num_rows ? $query->row : null;
    }

    private function getCustomer(int $customerId): ?array
    {
        $query = $this->db->query("SELECT * FROM `" . $this->tablePrefix . "customer` WHERE `customer_id` = '" . (int)$customerId . "'");
        return $query->num_rows ? $query->row : null;
    }

    private function getTotalInCurrency(array $orderData): ?float
    {
        try {
            $totalInCurrency = $this->registry->get('currency')->format($orderData['total'], $orderData['currency_code'], $orderData['currency_value'], false);
        } catch (\Throwable $e) {
            $totalInCurrency = $orderData['total'];
        }
        return $totalInCurrency !== null ? (float)$totalInCurrency : null;
    }

    public function parseRomanianAddress(string $address1, string $address2): array
    {
        $street = $address1;

        if (preg_match('/^(.*?)\s+(\d+\w{0,5})\s*$/u', $address1, $m)) {
            $street = trim($m[1]);
            $number = $m[2];
        } else {
            $number = $this->extractRomanianStreetNumber($address2);
        }
        if (strlen($number) > 10) {
            $number = substr($number, 0, 10);
        }
        $components = $this->extractRomanianAddressComponents($address2);

        return [
            'street' => $street,
            'house_number' => $number,
            'apartment_number' => $components['apartment'],
            'block' => $components['block'],
            'entrance' => $components['entrance'],
            'floor' => $components['floor']
        ];
    }

    private function extractRomanianStreetNumber(string $text): string
    {
        $text = trim($text);
        if ($text === '') {
            return '';
        }
        if (preg_match('/(?:^|[,;]\s*)nr\.?\s*(\d+\w{0,3})/iu', $text, $m)) {
            return $m[1];
        }
        if (preg_match('/^(\d+\w{0,3})(?:[,\s]|$)/u', $text, $m)) {
            return $m[1];
        }
        return '';
    }

    private function extractRomanianAddressComponents(string $text): array
    {
        $result = ['block' => '', 'entrance' => '', 'floor' => '', 'apartment' => ''];
        if (empty($text)) {
            return $result;
        }
        if (preg_match('/\b(?:bl(?:oc)?|block)\.?\s*([A-Za-z0-9]+)/iu', $text, $m)) {
            $result['block'] = $m[1];
        }
        if (preg_match('/\b(?:sc(?:ara)?|entrance)\.?\s*([A-Za-z0-9]+)/iu', $text, $m)) {
            $result['entrance'] = $m[1];
        }
        if (preg_match('/\b(?:et(?:aj)?|floor)\.?\s*([A-Za-z0-9]+)/iu', $text, $m)) {
            $result['floor'] = $m[1];
        }
        if (preg_match('/\b(?:ap(?:artament)?|apt)\.?\s*([A-Za-z0-9]+)/iu', $text, $m)) {
            $result['apartment'] = $m[1];
        }
        return $result;
    }

    private function getShippingMethodFromOrder(array $order): ?ShippingMethod
    {
        if (empty($order['shipping_method'])) {
            return null;
        }
        $shippingMethodData = json_decode($order['shipping_method'], true);
        if (!$shippingMethodData || empty($shippingMethodData['code'])) {
            return null;
        }
        $parts = explode(':', $shippingMethodData['code']);
        $shippingMethodId = is_array($parts) && count($parts) >= 2 ? $parts[count($parts) - 2] : null;
        if ($shippingMethodId === null || $shippingMethodId === '') {
            return null;
        }
        return $this->shippingMethodRepository->getShippingMethodById((int)$shippingMethodId);
    }

    private function determineIsPossibleCod(array $order): bool
    {
        $paymentMethod = json_decode($order['payment_method'] ?? '{}', true);
        $code = $paymentMethod['code'] ?? '';
        $name = $paymentMethod['name'] ?? '';
        if (substr($code, 0, 4) === 'cod.') {
            return true;
        }
        if (strtolower($name) === 'cash on delivery') {
            return true;
        }
        return false;
    }

    private function calculateDeclaredValue(bool $isPossiblyCod, float $orderTotal, float $templateValue): float
    {
        if ($isPossiblyCod && $this->currentRegion->code !== 'romania') {
            return $orderTotal;
        }

        $additionalSettingsData = $this->additionalSettingsDataProvider->getData();
        $autoFillDeclaredValueSet = array_key_exists('auto_declared_value', is_array($additionalSettingsData['additional_settings']) ? $additionalSettingsData['additional_settings'] : []);

        $autoDeclaredValue = $autoFillDeclaredValueSet && (bool) $additionalSettingsData['additional_settings']['auto_declared_value'];
        if ($autoDeclaredValue) {
            return ($templateValue > $orderTotal) ? $templateValue : $orderTotal;
        }
        return $templateValue ?: 0;
    }
}
