<?php
namespace Modules\Fiscal\Services;

use Gestoreasy\Core\Tenant\ConnectionManager;
use Gestoreasy\Core\Tenant\TenantContext;
use PDO;
use Exception;

/**
 * Serviço para mapeamento automático de produtos por código/EAN
 */
class ProdutoMapeamentoService {
    private PDO $pdo;
    private string $tenantId;

    public function __construct() {
        $this->pdo = ConnectionManager::forTenant();
        $this->tenantId = TenantContext::getTenantId();
    }

    /**
     * Mapeia item da nota para produto existente
     */
    public function mapearProduto(array $item): ?int {
        // Tentar por código do produto
        if (!empty($item['codigo_produto'])) {
            $produto = $this->buscarPorCodigo($item['codigo_produto']);
            if ($produto) {
                return $produto['id'];
            }
        }

        // Tentar por código de barras (EAN)
        if (!empty($item['codigo_barras'])) {
            $produto = $this->buscarPorEan($item['codigo_barras']);
            if ($produto) {
                return $produto['id'];
            }
        }

        // Tentar por NCM + Descrição (fuzzy match)
        if (!empty($item['ncm']) && !empty($item['descricao'])) {
            $produto = $this->buscarPorNcmDescricao($item['ncm'], $item['descricao']);
            if ($produto) {
                return $produto['id'];
            }
        }

        // Tentar apenas por descrição (fuzzy match)
        if (!empty($item['descricao'])) {
            $produto = $this->buscarPorDescricao($item['descricao']);
            if ($produto) {
                return $produto['id'];
            }
        }

        return null;
    }

    /**
     * Mapeia múltiplos itens
     */
    public function mapearItens(array $itens): array {
        $resultado = [];
        
        foreach ($itens as $item) {
            $productId = $this->mapearProduto($item);
            $resultado[] = [
                'item' => $item,
                'product_id' => $productId,
                'mapeado' => $productId !== null
            ];
        }

        return $resultado;
    }

    /**
     * Cria produto automaticamente se não encontrado
     */
    public function criarProdutoSeNaoExistir(array $item, bool $ativo = true): int {
        // Verificar se já existe
        $productId = $this->mapearProduto($item);
        if ($productId) {
            return $productId;
        }

        // Criar novo produto
        try {
            $stmt = $this->pdo->prepare('INSERT INTO products 
                (codigo, descricao, ean, ncm, unidade, preco_custo, ativo, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, NOW())');

            $codigo = $item['codigo_produto'] ?? $this->gerarCodigoAutomatico();
            $descricao = $item['descricao'] ?? 'Produto sem descrição';
            $ean = $item['codigo_barras'] ?? null;
            $ncm = $item['ncm'] ?? null;
            $unidade = $item['unidade'] ?? 'UN';
            $precoCusto = $item['valor_unitario'] ?? 0;

            $stmt->execute([
                $codigo,
                $descricao,
                $ean,
                $ncm,
                $unidade,
                $precoCusto,
                $ativo ? 1 : 0
            ]);

            return (int)$this->pdo->lastInsertId();
        } catch (Exception $e) {
            throw new Exception('Erro ao criar produto: ' . $e->getMessage());
        }
    }

    /**
     * Busca produto por código
     */
    private function buscarPorCodigo(string $codigo): ?array {
        $stmt = $this->pdo->prepare('SELECT id, codigo, descricao, ean, ncm FROM products WHERE codigo = ? LIMIT 1');
        $stmt->execute([$codigo]);
        $produto = $stmt->fetch();
        return $produto ?: null;
    }

    /**
     * Busca produto por EAN
     */
    private function buscarPorEan(string $ean): ?array {
        $stmt = $this->pdo->prepare('SELECT id, codigo, descricao, ean, ncm FROM products WHERE ean = ? LIMIT 1');
        $stmt->execute([$ean]);
        $produto = $stmt->fetch();
        return $produto ?: null;
    }

    /**
     * Busca produto por NCM e descrição (fuzzy match)
     */
    private function buscarPorNcmDescricao(string $ncm, string $descricao): ?array {
        // Buscar por NCM exato
        $stmt = $this->pdo->prepare('SELECT id, codigo, descricao, ean, ncm FROM products WHERE ncm = ? LIMIT 5');
        $stmt->execute([$ncm]);
        $produtos = $stmt->fetchAll();

        if (empty($produtos)) {
            return null;
        }

        // Fazer fuzzy match na descrição
        $descricaoLimpa = $this->limparDescricao($descricao);
        $melhorMatch = null;
        $melhorScore = 0;

        foreach ($produtos as $produto) {
            $score = $this->calcularSimilaridade($descricaoLimpa, $this->limparDescricao($produto['descricao']));
            if ($score > $melhorScore && $score > 0.7) { // 70% de similaridade
                $melhorScore = $score;
                $melhorMatch = $produto;
            }
        }

        return $melhorMatch;
    }

    /**
     * Busca produto por descrição (fuzzy match)
     */
    private function buscarPorDescricao(string $descricao): ?array {
        $descricaoLimpa = $this->limparDescricao($descricao);
        
        // Buscar produtos com palavras-chave similares
        $palavras = explode(' ', $descricaoLimpa);
        $palavras = array_filter($palavras, function($p) { return strlen($p) > 3; });
        
        if (empty($palavras)) {
            return null;
        }

        $condicoes = [];
        $params = [];
        foreach ($palavras as $palavra) {
            $condicoes[] = 'descricao LIKE ?';
            $params[] = '%' . $palavra . '%';
        }

        $sql = 'SELECT id, codigo, descricao, ean, ncm FROM products WHERE ' . implode(' OR ', $condicoes) . ' LIMIT 10';
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        $produtos = $stmt->fetchAll();

        if (empty($produtos)) {
            return null;
        }

        // Encontrar melhor match
        $melhorMatch = null;
        $melhorScore = 0;

        foreach ($produtos as $produto) {
            $score = $this->calcularSimilaridade($descricaoLimpa, $this->limparDescricao($produto['descricao']));
            if ($score > $melhorScore && $score > 0.6) { // 60% de similaridade
                $melhorScore = $score;
                $melhorMatch = $produto;
            }
        }

        return $melhorMatch;
    }

    /**
     * Limpa descrição para comparação
     */
    private function limparDescricao(string $descricao): string {
        $descricao = mb_strtolower($descricao, 'UTF-8');
        $descricao = preg_replace('/[^a-z0-9\s]/', '', $descricao);
        $descricao = preg_replace('/\s+/', ' ', $descricao);
        return trim($descricao);
    }

    /**
     * Calcula similaridade entre duas strings (Levenshtein)
     */
    private function calcularSimilaridade(string $str1, string $str2): float {
        $maxLen = max(strlen($str1), strlen($str2));
        if ($maxLen === 0) {
            return 1.0;
        }

        $distance = levenshtein($str1, $str2);
        return 1 - ($distance / $maxLen);
    }

    /**
     * Gera código automático para produto
     */
    private function gerarCodigoAutomatico(): string {
        // Buscar último código numérico
        $stmt = $this->pdo->query('SELECT MAX(CAST(codigo AS UNSIGNED)) AS max_codigo FROM products WHERE codigo REGEXP "^[0-9]+$"');
        $result = $stmt->fetch();
        $ultimoCodigo = (int)($result['max_codigo'] ?? 0);
        
        return str_pad((string)($ultimoCodigo + 1), 6, '0', STR_PAD_LEFT);
    }

    /**
     * Atualiza produto com dados da nota
     */
    public function atualizarProdutoComNota(int $productId, array $item): void {
        try {
            $updates = [];
            $params = [];

            // Atualizar preço de custo se fornecido
            if (isset($item['valor_unitario']) && $item['valor_unitario'] > 0) {
                $updates[] = 'preco_custo = ?';
                $params[] = $item['valor_unitario'];
            }

            // Atualizar NCM se não existir
            if (!empty($item['ncm'])) {
                $updates[] = 'ncm = COALESCE(ncm, ?)';
                $params[] = $item['ncm'];
            }

            // Atualizar EAN se não existir
            if (!empty($item['codigo_barras'])) {
                $updates[] = 'ean = COALESCE(ean, ?)';
                $params[] = $item['codigo_barras'];
            }

            if (!empty($updates)) {
                $params[] = $productId;
                $sql = 'UPDATE products SET ' . implode(', ', $updates) . ' WHERE id = ?';
                $stmt = $this->pdo->prepare($sql);
                $stmt->execute($params);
            }
        } catch (Exception $e) {
            // Ignorar erros de atualização
        }
    }
}

