<?php
/**
 * ShippingService
 * 
 * Service principal para cálculo de frete.
 * Orquestra múltiplos providers e gerencia cache.
 * 
 * @package Shipping
 */

namespace Shipping;

require_once __DIR__ . '/ShippingCalculator.php';
require_once __DIR__ . '/Providers/ShippingProviderInterface.php';
require_once __DIR__ . '/Providers/ManualShippingProvider.php';
require_once __DIR__ . '/Providers/MelhorEnvioProvider.php';

use Shipping\Providers\ShippingProviderInterface;
use Shipping\Providers\ManualShippingProvider;
use Shipping\Providers\MelhorEnvioProvider;

class ShippingService
{
    private \PDO $pdo;
    private int $storeId;
    private ShippingCalculator $calculator;
    private array $providers = [];
    private int $cacheTtlMinutes = 30;
    
    public function __construct(\PDO $pdo, int $storeId)
    {
        $this->pdo = $pdo;
        $this->storeId = $storeId;
        $this->calculator = new ShippingCalculator();
        
        // Registrar providers disponíveis
        $this->registerProvider(new ManualShippingProvider($pdo, $storeId));
        $this->registerProvider(new MelhorEnvioProvider($pdo, $storeId));
    }
    
    /**
     * Registra um provider de frete
     */
    public function registerProvider(ShippingProviderInterface $provider): void
    {
        $this->providers[$provider->getProviderName()] = $provider;
    }
    
    /**
     * Calcula opções de frete para o carrinho
     * 
     * @param array $cartItems Itens do carrinho (product_id, quantity, weight, dimensions)
     * @param string $destinationZipcode CEP de destino
     * @param float $cartTotal Total do carrinho (para frete grátis)
     * @return array Lista de opções de frete
     */
    public function calculateShippingOptions(
        array $cartItems,
        string $destinationZipcode,
        float $cartTotal = 0
    ): array {
        // Verificar cache
        $cacheKey = $this->generateCacheKey($cartItems, $destinationZipcode);
        $cached = $this->getFromCache($cacheKey);
        if ($cached !== null) {
            return $this->applyFreeShipping($cached, $cartTotal);
        }
        
        // Buscar CEP de origem da loja
        $originZipcode = $this->getStoreOriginZipcode();
        if (!$originZipcode) {
            return ['error' => 'Loja não configurou endereço de origem'];
        }
        
        // Buscar dados completos dos produtos se necessário
        $items = $this->enrichProductData($cartItems);
        
        // Coletar opções de todos os providers
        $allOptions = [];
        foreach ($this->providers as $provider) {
            if ($provider->isAvailable()) {
                try {
                    $options = $provider->calculateShipping(
                        $items,
                        $originZipcode,
                        $destinationZipcode,
                        $this->storeId
                    );
                    $allOptions = array_merge($allOptions, $options);
                } catch (\Exception $e) {
                    // Log error but continue with other providers
                    error_log("ShippingProvider error: " . $e->getMessage());
                }
            }
        }
        
        // Adicionar opção de Retirada no Local se configurada
        $pickupOption = $this->getLocalPickupOption();
        if ($pickupOption) {
            $allOptions[] = $pickupOption;
        }
        
        // Ordenar por preço
        usort($allOptions, fn($a, $b) => $a['price'] <=> $b['price']);
        
        // Salvar no cache
        $this->saveToCache($cacheKey, $allOptions);
        
        // Aplicar regras de frete grátis
        return $this->applyFreeShipping($allOptions, $cartTotal);
    }
    
    /**
     * Busca CEP de origem da loja
     */
    private function getStoreOriginZipcode(): ?string
    {
        $stmt = $this->pdo->prepare("SELECT origin_zipcode FROM stores WHERE id = ?");
        $stmt->execute([$this->storeId]);
        $result = $stmt->fetchColumn();
        return $result ?: null;
    }
    
    /**
     * Enriquece dados dos produtos com informações do banco
     */
    private function enrichProductData(array $cartItems): array
    {
        $productIds = array_column($cartItems, 'product_id');
        if (empty($productIds)) return $cartItems;
        
        $placeholders = str_repeat('?,', count($productIds) - 1) . '?';
        $stmt = $this->pdo->prepare("
            SELECT id, name, weight, height, width, length, requires_shipping
            FROM products 
            WHERE id IN ($placeholders) AND store_id = ?
        ");
        $stmt->execute([...$productIds, $this->storeId]);
        $products = $stmt->fetchAll(\PDO::FETCH_ASSOC);
        $productMap = array_column($products, null, 'id');
        
        $enriched = [];
        foreach ($cartItems as $item) {
            $pid = $item['product_id'];
            if (isset($productMap[$pid])) {
                $enriched[] = array_merge($item, $productMap[$pid]);
            } else {
                $enriched[] = $item;
            }
        }
        
        return $enriched;
    }
    
    /**
     * Aplica regras de frete grátis
     */
    private function applyFreeShipping(array $options, float $cartTotal): array
    {
        foreach ($options as &$option) {
            if (isset($option['free_shipping_above']) && $option['free_shipping_above'] > 0) {
                if ($cartTotal >= $option['free_shipping_above']) {
                    $option['original_price'] = $option['price'];
                    $option['price'] = 0;
                    $option['free_shipping'] = true;
                    $option['free_shipping_reason'] = "Frete grátis para compras acima de R$ " . number_format($option['free_shipping_above'], 2, ',', '.');
                }
            }
        }
        return $options;
    }
    
    /**
     * Busca opção de retirada no local
     */
    private function getLocalPickupOption(): ?array
    {
        $stmt = $this->pdo->prepare("
            SELECT * FROM shipping_methods 
            WHERE store_id = ? AND type = 'local_pickup' AND is_active = 1
            LIMIT 1
        ");
        $stmt->execute([$this->storeId]);
        $method = $stmt->fetch(\PDO::FETCH_ASSOC);
        
        if ($method) {
            return [
                'id' => $method['id'],
                'carrier' => 'Retirada',
                'service' => $method['name'],
                'price' => 0,
                'delivery_time' => 'Imediato',
                'min_days' => 0,
                'max_days' => 0,
                'is_pickup' => true
            ];
        }
        
        return null;
    }
    
    /**
     * Gera chave de cache única
     */
    private function generateCacheKey(array $items, string $zipcode): string
    {
        $key = $this->storeId . '_' . $zipcode . '_' . json_encode(
            array_map(fn($i) => $i['product_id'] . ':' . ($i['quantity'] ?? 1), $items)
        );
        return md5($key);
    }
    
    /**
     * Busca do cache
     */
    private function getFromCache(string $cacheKey): ?array
    {
        $stmt = $this->pdo->prepare("
            SELECT quotes FROM shipping_quotes_cache 
            WHERE store_id = ? AND cache_key = ? AND expires_at > NOW()
            LIMIT 1
        ");
        $stmt->execute([$this->storeId, $cacheKey]);
        $result = $stmt->fetchColumn();
        
        return $result ? json_decode($result, true) : null;
    }
    
    /**
     * Salva no cache
     */
    private function saveToCache(string $cacheKey, array $quotes): void
    {
        $expiresAt = date('Y-m-d H:i:s', strtotime("+{$this->cacheTtlMinutes} minutes"));
        
        $stmt = $this->pdo->prepare("
            INSERT INTO shipping_quotes_cache (store_id, cache_key, quotes, expires_at)
            VALUES (?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE quotes = VALUES(quotes), expires_at = VALUES(expires_at)
        ");
        $stmt->execute([$this->storeId, $cacheKey, json_encode($quotes), $expiresAt]);
    }
    
    /**
     * Retorna o calculador para uso externo
     */
    public function getCalculator(): ShippingCalculator
    {
        return $this->calculator;
    }
}
