<?php
namespace EasyChat\Library\Delivery;

use EasyChat\Library\Commerce\CommerceContextCache;
use EasyChat\Library\Logger;





final class ShipmentTrackingService
{
    private $providers;
    private $credential_scopes = array();

    public function __construct($registry, ?array $providers = null)
    {
        if ($providers !== null) {
            $this->providers = $providers;
            return;
        }
        $connection = new DeliveryConnection($registry);
        $post = $connection->credentials('russian_post');
        $cdek = $connection->credentials('cdek');
        $yandex = $connection->credentials('yandex_delivery');
        $apiship = $connection->credentials('apiship');
        $this->providers = array();
        if (!empty($post['enabled'])) {
            $this->providers[] = new RussianPostTrackingProvider($registry, (string)($post['login'] ?? ''), (string)($post['password'] ?? ''));
            $this->credential_scopes['russian_post'] = hash('sha256', (string)($post['login'] ?? '') . "\0" . (string)($post['password'] ?? ''));
        }
        if (!empty($cdek['enabled'])) {
            $this->providers[] = new CdekTrackingProvider($registry, (string)($cdek['client_id'] ?? ''), (string)($cdek['client_secret'] ?? ''));
            $this->credential_scopes['cdek'] = hash('sha256', (string)($cdek['client_id'] ?? '') . "\0" . (string)($cdek['client_secret'] ?? ''));
        }
        if (!empty($yandex['enabled'])) {
            $this->providers[] = new YandexDeliveryTrackingProvider($registry, (string)($yandex['token'] ?? ''));
            $this->credential_scopes['yandex_delivery'] = hash('sha256', (string)($yandex['token'] ?? ''));
        }
        if (!empty($apiship['enabled'])) {
            $this->providers[] = new ApiShipTrackingProvider($registry, (string)($apiship['token'] ?? ''));
            $this->credential_scopes['apiship'] = hash('sha256', (string)($apiship['token'] ?? ''));
        }
    }

    public function enrich(array $shipments, string $order_fulfillment_status = '', bool $force_refresh = false): array
    {
        $result = array();
        foreach (array_slice($shipments, 0, 4) as $shipment) {
            if (!is_array($shipment)) continue;
             
             
            if (!$force_refresh && $this->isTerminalStatus($order_fulfillment_status)) {
                $result[] = $this->safeShipment($shipment);
                continue;
            }
            $carrier = $this->plain($shipment['carrier'] ?? '', 80);
            $number = preg_replace('/\s+/', '', trim((string)($shipment['tracking_number'] ?? '')));
            if ($number === '' || !preg_match('/^[A-Za-z0-9_.:-]{1,80}$/', $number)) {
                $result[] = $this->safeShipment($shipment);
                continue;
            }
             
             
            if ($carrier === '') {
                $result[] = $this->safeShipment($shipment);
                continue;
            }
            $matched = null;
            foreach ($this->providers as $provider) {
                if ($provider instanceof TrackingProviderInterface && $provider->supports($carrier, $number)) {
                    $matched = $provider;
                    break;
                }
            }
            if (!$matched) {
                $shipment['tracking_number'] = $number;
                $result[] = $this->safeShipment($shipment);
                continue;
            }
            $cache = new CommerceContextCache();
             
             
            $identity = $number;
            if ($matched->code() === 'apiship' && preg_match('/^[1-9][0-9]{0,18}$/', (string)($shipment['external_order_id'] ?? ''))) {
                $identity .= ':' . (string)$shipment['external_order_id'];
            }
            $cache_key = $cache->trackingKey($matched->code(), $identity, (string)($this->credential_scopes[$matched->code()] ?? 'default'));
            $cached = $force_refresh ? array() : $cache->getTracking($cache_key, true);
            if ($cached !== array()) {
                $shipment = array_merge($shipment, (array)($cached['shipment'] ?? array()));
                $result[] = $this->safeShipment($shipment);
                continue;
            }
             
             
            if (!$cache->claimTracking($cache_key, 45)) {
                 
                 
                usleep(200000);
                $cached = $cache->getTracking($cache_key, true);
                if ($cached !== array()) {
                    $shipment = array_merge($shipment, (array)($cached['shipment'] ?? array()));
                    $result[] = $this->safeShipment($shipment);
                    continue;
                }
                $shipment['tracking_url'] = $matched->trackingUrl($number);
                $shipment['tracking_state'] = 'refreshing';
                $result[] = $this->safeShipment($shipment);
                continue;
            }
            try {
                $tracked = $matched instanceof ShipmentAwareTrackingProviderInterface
                    ? $matched->trackShipment($shipment)
                    : $matched->track($number);
                if (($tracked['status'] ?? '') === 'ready' && is_array($tracked['shipment'] ?? null)) {
                    $shipment = array_merge($shipment, $tracked['shipment']);
                    $shipment['events'] = array_slice((array)($tracked['events'] ?? array()), 0, 20);
                    $shipment['source'] = $matched->code();
                } else {
                    $shipment['tracking_url'] = $matched->trackingUrl($number);
                    $shipment['tracking_state'] = (string)($tracked['status'] ?? 'unavailable');
                }
                $cache->putTracking($cache_key, $shipment, $this->trackingTtl($shipment, $tracked));
                Logger::info('delivery_tracking provider=' . $matched->code() . ' outcome=' . (string)($tracked['status'] ?? 'unknown') . ' cache=miss', 'delivery.log');
            } catch (\Throwable $e) {
                $shipment['tracking_url'] = $matched->trackingUrl($number);
                $shipment['tracking_state'] = 'unavailable';
                $cache->putTracking($cache_key, $shipment, 60);
                Logger::warning('delivery_tracking provider=' . $matched->code() . ' outcome=exception cache=negative', 'delivery.log');
            } finally {
                $cache->releaseTracking($cache_key);
            }
            $result[] = $this->safeShipment($shipment);
        }
        return $result;
    }

    private function isTerminalStatus(string $status): bool
    {
        return in_array(strtolower(trim($status)), array('delivered', 'cancelled', 'returned', 'refunded'), true);
    }

    private function trackingTtl(array $shipment, array $tracked): int
    {
        $status = strtolower((string)($shipment['status'] ?? ''));
        if (in_array($status, array('cancelled', 'refunded'), true)) return 86400;
        if (TrackingStatus::isTerminal($status)) return TrackingStatus::refreshTtl($status);
        if (($tracked['status'] ?? '') !== 'ready') return 60;
        return TrackingStatus::refreshTtl($status);
    }

    private function safeShipment(array $shipment): array
    {
        $events = array();
        foreach (array_slice((array)($shipment['events'] ?? array()), 0, 20) as $event) {
            if (!is_array($event)) continue;
            $events[] = array(
                'status' => preg_replace('/[^a-z_]/', '', (string)($event['status'] ?? 'unknown')),
                'label' => $this->plain($event['label'] ?? '', 160),
                'date' => $this->plain($event['date'] ?? '', 40),
                'location' => $this->plain($event['location'] ?? '', 160),
            );
        }
        $url = trim((string)($shipment['tracking_url'] ?? ''));
        $parts = $url !== '' ? parse_url($url) : array();
        if ($url !== '' && (!is_array($parts) || strtolower((string)($parts['scheme'] ?? '')) !== 'https')) $url = '';
        return array(
            'carrier' => $this->plain($shipment['carrier'] ?? '', 80),
            'carrier_code' => preg_replace('/[^a-z0-9_]/', '', strtolower((string)($shipment['carrier_code'] ?? ''))),
            'tracking_number' => mb_substr(preg_replace('/[^A-Za-z0-9_.:-]/', '', (string)($shipment['tracking_number'] ?? '')), 0, 80, 'UTF-8'),
            'status' => preg_replace('/[^a-z_]/', '', (string)($shipment['status'] ?? TrackingStatus::UNKNOWN)),
            'status_label' => $this->plain($shipment['status_label'] ?? '', 160),
            'updated_at' => $this->plain($shipment['updated_at'] ?? '', 40),
            'eta' => $this->plain($shipment['eta'] ?? '', 40),
            'pickup_point' => $this->plain($shipment['pickup_point'] ?? '', 160),
            'location' => $this->plain($shipment['location'] ?? '', 160),
            'tracking_url' => $url,
            'tracking_state' => preg_replace('/[^a-z_]/', '', (string)($shipment['tracking_state'] ?? '')),
            'events' => $events,
        );
    }

    private function plain($value, int $limit): string
    {
        return mb_substr(trim(preg_replace('/\s+/u', ' ', strip_tags((string)$value))), 0, $limit, 'UTF-8');
    }
}
