<?php

namespace App\Repositories;

use App\Core\Cache;
use App\Core\Lojas;

class EstoqueRepository
{
    public function __construct(private \PDO $pdo) {}

    /**
     * Empresa (loja) ativa. TODAS as consultas de estoque são restritas a ela —
     * o banco tem várias empresas (4/5/6) e o gestor quer ver SÓ a empresa 5 (Mega).
     * Em outras lojas, devolve a empresa daquela loja (multi-loja continua correto).
     */
    private function empresa(): int
    {
        try { $e = (int) Lojas::codEmpresaAtiva(); return $e ?: 5; }
        catch (\Throwable) { return 5; }
    }

    /** Converte DESCRICAO/COD_UNIDADE WIN1252 → UTF-8 (senão json_encode esvazia em acentos). */
    private function conv(array $rows): array
    {
        foreach ($rows as &$r) {
            foreach (['DESCRICAO','COD_UNIDADE'] as $k) {
                if (isset($r[$k]) && $r[$k] !== '' && !mb_check_encoding((string)$r[$k], 'UTF-8')) {
                    $r[$k] = mb_convert_encoding((string)$r[$k], 'UTF-8', 'Windows-1252');
                }
            }
        }
        unset($r);
        return $rows;
    }

    public function kpis(): array
    {
        $emp = $this->empresa();
        return Cache::lembrar("estoque_kpis_{$emp}", 3600, function () use ($emp) {
            $sql = "SELECT
                COUNT(DISTINCT ie.COD_ITEM) TOTAL_ITENS,
                SUM(CASE WHEN ie.QTD > 0 THEN 1 ELSE 0 END) ITENS_POSITIVO,
                SUM(CASE WHEN ie.QTD <= 0 THEN 1 ELSE 0 END) ITENS_ZERADO,
                SUM(CAST(ie.QTD AS DOUBLE PRECISION) * ie.CUSTO_FINAL) VALOR_CUSTO,
                SUM(CAST(ie.QTD AS DOUBLE PRECISION) * ie.PRECO_VENDA) VALOR_VENDA
            FROM ITENS_ESTOQUE ie
            JOIN ITENS i ON ie.COD_ITEM = i.COD_ITEM
            WHERE ie.COD_EMPRESA = {$emp}";
            return $this->pdo->query($sql)->fetch(\PDO::FETCH_ASSOC) ?: [];
        });
    }

    public function kpisMinimos(): array
    {
        $emp = $this->empresa();
        return Cache::lembrar("estoque_kpis_minimos_{$emp}", 3600, function () use ($emp) {
            $sql = "SELECT
                COUNT(*) TOTAL_ABAIXO,
                SUM(i.ESTOQ_MINIMO - ie.QTD) DEFICIT_TOTAL
            FROM ITENS_ESTOQUE ie
            JOIN ITENS i ON ie.COD_ITEM = i.COD_ITEM
            WHERE ie.COD_EMPRESA = {$emp}
              AND i.ESTOQ_MINIMO > 0 AND ie.QTD < i.ESTOQ_MINIMO";
            return $this->pdo->query($sql)->fetch(\PDO::FETCH_ASSOC) ?: [];
        });
    }

    public function posicao(string $busca = '', int $limite = 200): array
    {
        $emp = $this->empresa();
        $where = $busca ? "AND UPPER(i.DESCRICAO) CONTAINING UPPER(?)" : '';
        $sql = "SELECT FIRST {$limite}
            ie.COD_ITEM,
            i.DESCRICAO,
            i.COD_UNIDADE,
            ie.QTD,
            ie.QTD_RESERVADA,
            ie.PRECO_VENDA,
            ie.CUSTO_FINAL,
            ie.CUSTO_MEDIO,
            i.ESTOQ_MINIMO,
            i.ESTOQUE_MAXIMO,
            ie.DT_ATUALIZACAO
        FROM ITENS_ESTOQUE ie
        JOIN ITENS i ON ie.COD_ITEM = i.COD_ITEM
        WHERE ie.COD_EMPRESA = {$emp} AND ie.QTD > 0 {$where}
        ORDER BY CAST(ie.QTD AS DOUBLE PRECISION) * ie.PRECO_VENDA DESC";
        $st = $this->pdo->prepare($sql);
        $busca ? $st->execute([$busca]) : $st->execute();
        return $this->conv($st->fetchAll(\PDO::FETCH_ASSOC));
    }

    public function minimos(int $limite = 300): array
    {
        $emp = $this->empresa();
        $sql = "SELECT FIRST {$limite}
            ie.COD_ITEM,
            i.DESCRICAO,
            i.COD_UNIDADE,
            ie.QTD,
            i.ESTOQ_MINIMO,
            (i.ESTOQ_MINIMO - ie.QTD) DEFICIT,
            ie.PRECO_VENDA,
            ie.CUSTO_FINAL,
            ie.DT_ATUALIZACAO
        FROM ITENS_ESTOQUE ie
        JOIN ITENS i ON ie.COD_ITEM = i.COD_ITEM
        WHERE ie.COD_EMPRESA = {$emp}
          AND i.ESTOQ_MINIMO > 0 AND ie.QTD < i.ESTOQ_MINIMO
        ORDER BY (i.ESTOQ_MINIMO - ie.QTD) DESC";
        return $this->conv($this->pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC));
    }

    public function giro(string $dataIni, string $dataFim, int $limite = 200): array
    {
        $hoje   = date('Y-m-d');
        $emp    = $this->empresa();
        // Giro de 30 dias muda pouco durante o dia → cache mais longo deixa o card instantâneo.
        $ttl    = ($dataFim >= $hoje) ? 1800 : 86400; // 30min se inclui hoje, 24h se histórico
        $chave  = "estoque_giro_{$emp}_{$dataIni}_{$dataFim}_{$limite}";

        // Período em dias (p/ taxa de venda diária correta — antes era fixo 30).
        $dias = max(1, (int) round((strtotime($dataFim) - strtotime($dataIni)) / 86400) + 1);

        // Bancos remotos via VPN com muitas notas (ex.: Real Prime — bar) podem passar de 30s.
        @set_time_limit(180);

        return Cache::lembrar($chave, $ttl, function () use ($dataIni, $dataFim, $limite, $dias, $emp) {
            // OTIMIZADO: dirige pela AGREGAÇÃO de vendas (poucos itens vendidos no período)
            // e só então junta ao estoque/itens. A versão antiga varria e ORDENAVA os ~43k
            // itens de estoque p/ pegar só os 200 primeiros — levava >50s; esta leva ~2s.
            $sql = "SELECT FIRST {$limite}
                v.COD_ITEM,
                i.DESCRICAO,
                i.COD_UNIDADE,
                ie.QTD              ESTOQUE_ATUAL,
                ie.CUSTO_FINAL,
                ie.PRECO_VENDA,
                v.QTD_VENDIDA,
                v.FAT_VENDIDA,
                CASE WHEN v.QTD_VENDIDA > 0
                     THEN ie.QTD / (v.QTD_VENDIDA / {$dias}.0)
                     ELSE 0
                END DIAS_COBERTURA
            FROM (
                SELECT ni.COD_ITEM,
                       SUM(ni.QTD) QTD_VENDIDA,
                       SUM(ni.QTD * ni.PRECO_UNIT) FAT_VENDIDA
                FROM NOTAS_ITENS ni
                JOIN NOTAS_CAB nc ON nc.NR_NOTA = ni.NR_NOTA
                                  AND nc.COD_TIPO_OPERACAO = ni.COD_TIPO_OPERACAO
                WHERE nc.COD_TIPO_OPERACAO IN (115,231,297)
                  AND nc.COD_SITUACAO <> 3
                  AND nc.COD_EMPRESA = {$emp}
                  AND nc.DATA_EMISSAO BETWEEN ? AND ?
                GROUP BY ni.COD_ITEM
            ) v
            JOIN ITENS_ESTOQUE ie ON ie.COD_ITEM = v.COD_ITEM AND ie.COD_EMPRESA = {$emp} AND ie.QTD > 0
            JOIN ITENS i          ON i.COD_ITEM  = v.COD_ITEM
            ORDER BY v.QTD_VENDIDA DESC";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $this->conv($st->fetchAll(\PDO::FETCH_ASSOC));
        });
    }

    public function margem(int $limite = 200): array
    {
        $emp = $this->empresa();
        return Cache::lembrar("estoque_margem_{$emp}_{$limite}", 3600, function () use ($limite, $emp) {
            $sql = "SELECT FIRST {$limite}
                ie.COD_ITEM,
                i.DESCRICAO,
                i.COD_UNIDADE,
                ie.QTD,
                ie.CUSTO_FINAL,
                ie.CUSTO_MEDIO,
                ie.PRECO_VENDA,
                CASE WHEN ie.PRECO_VENDA > 0
                     THEN (ie.PRECO_VENDA - ie.CUSTO_FINAL) / ie.PRECO_VENDA * 100
                     ELSE 0
                END MARGEM_PERC,
                (ie.PRECO_VENDA - ie.CUSTO_FINAL) MARGEM_VALOR,
                CAST(ie.QTD AS DOUBLE PRECISION) * ie.PRECO_VENDA VALOR_ESTOQUE_VENDA,
                CAST(ie.QTD AS DOUBLE PRECISION) * ie.CUSTO_FINAL VALOR_ESTOQUE_CUSTO
            FROM ITENS_ESTOQUE ie
            JOIN ITENS i ON ie.COD_ITEM = i.COD_ITEM
            WHERE ie.COD_EMPRESA = {$emp} AND ie.PRECO_VENDA > 0 AND ie.QTD > 0
            ORDER BY MARGEM_PERC DESC";
            return $this->conv($this->pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC));
        });
    }

    public function inventario(int $limite = 500): array
    {
        $emp = $this->empresa();
        return Cache::lembrar("estoque_inventario_{$emp}_{$limite}", 3600, function () use ($limite, $emp) {
            $sql = "SELECT FIRST {$limite}
                ie.COD_ITEM,
                i.DESCRICAO,
                i.COD_UNIDADE,
                ie.QTD,
                ie.QTD_RESERVADA,
                ie.PRECO_VENDA,
                ie.CUSTO_FINAL,
                ie.CUSTO_MEDIO,
                ie.CUSTO_COMPRA,
                ie.DT_ATUALIZACAO,
                i.ESTOQ_MINIMO
            FROM ITENS_ESTOQUE ie
            JOIN ITENS i ON ie.COD_ITEM = i.COD_ITEM
            WHERE ie.COD_EMPRESA = {$emp}
            ORDER BY i.DESCRICAO";
            return $this->conv($this->pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC));
        });
    }

    public function ultimaData(): string
    {
        $emp = $this->empresa();
        return Cache::lembrar("estoque_ultima_data_{$emp}", 600, function () use ($emp) {
            try {
                // Janela recente (120d) — MAX sem limite custa ~45s no Firebird 2.5 sem índice descendente.
                $r = $this->pdo->query(
                    "SELECT MAX(nc.DATA_EMISSAO)
                     FROM NOTAS_CAB nc
                     JOIN TIPO_OPERACAO t ON t.COD_TIPO_OPERACAO = nc.COD_TIPO_OPERACAO
                                        AND t.TIPO_NATUREZA = 'V'
                     WHERE nc.COD_SITUACAO <> 3
                       AND nc.COD_EMPRESA = {$emp}
                       AND nc.DATA_EMISSAO >= DATEADD(DAY,-120,CURRENT_DATE)
                       AND nc.DATA_EMISSAO <= CURRENT_DATE"
                )->fetchColumn();
                if (!$r) return date('Y-m-d');
                if ($r instanceof \DateTime) return $r->format('Y-m-d');
                return substr((string)$r, 0, 10);
            } catch (\Throwable) { return date('Y-m-d'); }
        });
    }
}
