<?php

namespace Modules\IA\Services;

use PDO;
use Exception;

/**
 * Serviço para previsão de demanda usando análise de séries temporais
 */
class PrevisaoDemandaService {
    private PDO $pdo;
    private string $tenantId;

    public function __construct(string $tenantId) {
        $this->tenantId = $tenantId;
        $this->pdo = \Gestoreasy\Core\Tenant\ConnectionManager::forTenant($tenantId);
    }

    /**
     * Previsão de demanda para um produto
     */
    public function preverDemanda(int $productId, int $mesesFuturos = 3): array {
        // Buscar histórico de vendas
        $historico = $this->buscarHistorico($productId, 12); // Últimos 12 meses

        if (count($historico) < 3) {
            throw new Exception('Histórico insuficiente para previsão (mínimo 3 meses)');
        }

        // Calcular média móvel
        $mediaMovel = $this->calcularMediaMovel($historico, 3);

        // Calcular tendência
        $tendencia = $this->calcularTendencia($historico);

        // Calcular sazonalidade
        $sazonalidade = $this->calcularSazonalidade($historico);

        // Gerar previsões
        $previsoes = [];
        $ultimoMes = end($historico);
        $anoAtual = (int)date('Y');
        $mesAtual = (int)date('n');

        for ($i = 1; $i <= $mesesFuturos; $i++) {
            $mes = $mesAtual + $i;
            $ano = $anoAtual;
            
            if ($mes > 12) {
                $mes -= 12;
                $ano++;
            }

            // Previsão = média móvel + tendência + sazonalidade
            $previsao = $mediaMovel + ($tendencia * $i) + ($sazonalidade[$mes] ?? 0);
            $previsao = max(0, $previsao); // Não pode ser negativa

            // Calcular confiança (baseado na variância do histórico)
            $confianca = $this->calcularConfianca($historico);

            $previsoes[] = [
                'ano' => $ano,
                'mes' => $mes,
                'demanda_prevista' => round($previsao, 2),
                'confianca' => round($confianca, 2),
                'tendencia' => $tendencia > 0 ? 'crescente' : ($tendencia < 0 ? 'decrescente' : 'estavel')
            ];

            // Salvar previsão
            $this->salvarPrevisao($productId, $ano, $mes, $previsao, $confianca);
        }

        return [
            'product_id' => $productId,
            'previsoes' => $previsoes,
            'media_movel' => round($mediaMovel, 2),
            'tendencia' => round($tendencia, 2)
        ];
    }

    /**
     * Busca histórico de vendas
     */
    private function buscarHistorico(int $productId, int $meses): array {
        $dataInicio = date('Y-m-01', strtotime("-{$meses} months"));
        
        $stmt = $this->pdo->prepare('
            SELECT 
                DATE_FORMAT(so.created_at, "%Y-%m") as periodo,
                YEAR(so.created_at) as ano,
                MONTH(so.created_at) as mes,
                SUM(soi.quantidade) as quantidade_vendida,
                SUM(soi.total) as valor_total
            FROM sales_orders so
            INNER JOIN sales_order_items soi ON so.id = soi.order_id
            WHERE soi.product_id = ?
            AND so.status = "finalizado"
            AND DATE(so.created_at) >= ?
            GROUP BY periodo, ano, mes
            ORDER BY ano ASC, mes ASC
        ');
        $stmt->execute([$productId, $dataInicio]);
        $resultados = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Preencher meses sem vendas com 0
        $historico = [];
        $dataAtual = strtotime($dataInicio);
        $dataFim = time();

        while ($dataAtual <= $dataFim) {
            $ano = (int)date('Y', $dataAtual);
            $mes = (int)date('n', $dataAtual);
            $chave = "{$ano}-{$mes}";

            $encontrado = false;
            foreach ($resultados as $resultado) {
                if ($resultado['ano'] == $ano && $resultado['mes'] == $mes) {
                    $historico[] = [
                        'ano' => $ano,
                        'mes' => $mes,
                        'quantidade' => (float)$resultado['quantidade_vendida'],
                        'valor' => (float)$resultado['valor_total']
                    ];
                    $encontrado = true;
                    break;
                }
            }

            if (!$encontrado) {
                $historico[] = [
                    'ano' => $ano,
                    'mes' => $mes,
                    'quantidade' => 0,
                    'valor' => 0
                ];
            }

            $dataAtual = strtotime('+1 month', $dataAtual);
        }

        return $historico;
    }

    /**
     * Calcula média móvel
     */
    private function calcularMediaMovel(array $historico, int $periodos): float {
        $quantidades = array_column($historico, 'quantidade');
        $ultimos = array_slice($quantidades, -$periodos);
        return array_sum($ultimos) / count($ultimos);
    }

    /**
     * Calcula tendência (regressão linear simples)
     */
    private function calcularTendencia(array $historico): float {
        $n = count($historico);
        if ($n < 2) {
            return 0;
        }

        $x = range(1, $n);
        $y = array_column($historico, 'quantidade');

        $somaX = array_sum($x);
        $somaY = array_sum($y);
        $somaXY = 0;
        $somaX2 = 0;

        for ($i = 0; $i < $n; $i++) {
            $somaXY += $x[$i] * $y[$i];
            $somaX2 += $x[$i] * $x[$i];
        }

        $denominador = ($n * $somaX2) - ($somaX * $somaX);
        if ($denominador == 0) {
            return 0;
        }

        $inclinacao = (($n * $somaXY) - ($somaX * $somaY)) / $denominador;
        return $inclinacao;
    }

    /**
     * Calcula sazonalidade
     */
    private function calcularSazonalidade(array $historico): array {
        $sazonalidade = array_fill(1, 12, 0);
        $contadores = array_fill(1, 12, 0);

        foreach ($historico as $item) {
            $mes = $item['mes'];
            $sazonalidade[$mes] += $item['quantidade'];
            $contadores[$mes]++;
        }

        // Calcular média por mês
        $mediaGeral = array_sum(array_column($historico, 'quantidade')) / count($historico);

        for ($mes = 1; $mes <= 12; $mes++) {
            if ($contadores[$mes] > 0) {
                $mediaMes = $sazonalidade[$mes] / $contadores[$mes];
                $sazonalidade[$mes] = $mediaMes - $mediaGeral;
            }
        }

        return $sazonalidade;
    }

    /**
     * Calcula confiança da previsão
     */
    private function calcularConfianca(array $historico): float {
        $quantidades = array_column($historico, 'quantidade');
        $media = array_sum($quantidades) / count($quantidades);
        
        $variancia = 0;
        foreach ($quantidades as $qtd) {
            $variancia += pow($qtd - $media, 2);
        }
        $variancia /= count($quantidades);
        $desvioPadrao = sqrt($variancia);

        // Coeficiente de variação
        $cv = $media > 0 ? ($desvioPadrao / $media) : 1;
        
        // Confiança inversamente proporcional ao CV
        $confianca = max(0, min(100, 100 - ($cv * 50)));

        return $confianca;
    }

    /**
     * Salva previsão
     */
    private function salvarPrevisao(int $productId, int $ano, int $mes, float $demanda, float $confianca): void {
        $stmt = $this->pdo->prepare('
            INSERT INTO demanda_previsoes (
                tenant_id, product_id, ano, mes,
                demanda_prevista, confianca
            ) VALUES (?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE
                demanda_prevista = VALUES(demanda_prevista),
                confianca = VALUES(confianca),
                updated_at = NOW()
        ');
        $stmt->execute([
            $this->tenantId,
            $productId,
            $ano,
            $mes,
            $demanda,
            $confianca
        ]);
    }

    /**
     * Treina modelo de previsão
     */
    public function treinarModelo(int $productId): array {
        // Buscar histórico completo
        $historico = $this->buscarHistorico($productId, 24); // Últimos 24 meses

        if (count($historico) < 6) {
            throw new Exception('Histórico insuficiente para treinamento (mínimo 6 meses)');
        }

        // Calcular métricas do modelo
        $precisao = $this->calcularPrecisaoModelo($productId, $historico);

        // Salvar modelo
        $stmt = $this->pdo->prepare('
            INSERT INTO ml_modelos (
                tenant_id, tipo, nome, versao, status,
                precisao, data_treinamento, total_registros_treinamento
            ) VALUES (?, "previsao_demanda", ?, "1.0", "ativo", ?, NOW(), ?)
        ');
        $stmt->execute([
            $this->tenantId,
            "Previsão Demanda - Produto {$productId}",
            $precisao,
            count($historico)
        ]);

        return [
            'product_id' => $productId,
            'precisao' => $precisao,
            'total_registros' => count($historico),
            'status' => 'treinado'
        ];
    }

    /**
     * Calcula precisão do modelo
     */
    private function calcularPrecisaoModelo(int $productId, array $historico): float {
        // Usar últimos 3 meses para validação
        $validacao = array_slice($historico, -3);
        $treinamento = array_slice($historico, 0, -3);

        if (count($treinamento) < 3) {
            return 0;
        }

        // Gerar previsões para período de validação
        $erros = [];
        foreach ($validacao as $item) {
            // Previsão baseada em média móvel do treinamento
            $mediaMovel = $this->calcularMediaMovel($treinamento, 3);
            $previsao = $mediaMovel;
            $real = $item['quantidade'];

            if ($real > 0) {
                $erro = abs($previsao - $real) / $real;
                $erros[] = $erro;
            }
        }

        if (empty($erros)) {
            return 0;
        }

        $mae = array_sum($erros) / count($erros); // Mean Absolute Error
        $precisao = max(0, min(100, (1 - $mae) * 100));

        return round($precisao, 2);
    }
}

