<?php

namespace App\Repositories;

use App\Core\Cache;
use App\Core\Database;

/**
 * Todas as consultas SQL do módulo Fechamento de Caixa.
 * Sem regra de negócio — apenas acesso ao banco Firebird.
 */
class FechamentoCaixaRepository
{
    private \PDO $pdo;
    private int  $codEmpresa;

    public function __construct()
    {
        $this->pdo        = Database::conectar();
        $this->codEmpresa = \App\Core\Lojas::codEmpresaAtiva() ?: 5;
    }

    // =========================================================================
    // FILTROS / LISTAS
    // =========================================================================

    /** Data do último fechamento registrado (usada como default do filtro) */
    public function ultimaDataFechamento(): ?string
    {
        try {
            $val = $this->pdo->query("
                SELECT FIRST 1 DATA_FECHAMENTO
                FROM ABERTURA_CAIXA
                WHERE COD_EMPRESA = {$this->codEmpresa}
                  AND STATUS = 'F'
                  AND DATA_FECHAMENTO IS NOT NULL
                ORDER BY DATA_FECHAMENTO DESC
            ")->fetchColumn();
            if (!$val) return null;
            if ($val instanceof \DateTime) return $val->format('Y-m-d');
            return substr(trim((string)$val), 0, 10);
        } catch (\Throwable) {
            return null;
        }
    }

    /** Lista de operadores para autocomplete do filtro */
    public function listarOperadores(): array
    {
        return $this->pdo->query("
            SELECT DISTINCT TRIM(COD_USUARIO_FECHAMENTO) AS OPERADOR
            FROM ABERTURA_CAIXA
            WHERE COD_EMPRESA = {$this->codEmpresa}
              AND COD_USUARIO_FECHAMENTO IS NOT NULL
              AND TRIM(COD_USUARIO_FECHAMENTO) <> ''
            ORDER BY 1
        ")->fetchAll(\PDO::FETCH_COLUMN);
    }

    // =========================================================================
    // FECHAMENTOS
    // =========================================================================

    /**
     * Lista fechamentos com busca parcial (LIKE) por operador.
     * Inclui NOME_TERMINAL para exibição no card.
     * UPPER/TRIM removidos do WHERE para permitir uso de índice.
     */
    public function listarFechamentos(string $busca, string $dataIni, string $dataFim): array
    {
        $ttl   = ($dataFim >= date('Y-m-d')) ? 300 : 86400;
        $chave = 'fc_fechamentos_' . md5($busca . $dataIni . $dataFim . $this->codEmpresa);
        return Cache::lembrar($chave, $ttl, function () use ($busca, $dataIni, $dataFim) {
            $like = $this->pdo->quote('%' . trim($busca) . '%');
            return $this->pdo->query("
                SELECT
                    ac.COD_ABERT_CAIXA,
                    ac.DATA_ABERTURA,
                    ac.HORA_ABERTURA,
                    ac.DATA_FECHAMENTO,
                    ac.HORA_FECHAMENTO,
                    ac.STATUS,
                    TRIM(ac.COD_USUARIO_ABERTURA)    AS OPERADOR_ABERTURA,
                    TRIM(ac.COD_USUARIO_FECHAMENTO)  AS OPERADOR,
                    COALESCE(TRIM(ac.NOME_TERMINAL), '') AS TERMINAL,
                    COALESCE(ac.SUPRIMENTO, 0)       AS SUPRIMENTO,
                    COALESCE(ac.VALOR_CALCULADO, 0)  AS VALOR_CALCULADO,
                    COALESCE(ac.VALOR_INFORMADO, 0)  AS VALOR_INFORMADO
                FROM ABERTURA_CAIXA ac
                WHERE ac.COD_EMPRESA = {$this->codEmpresa}
                  AND ac.COD_USUARIO_FECHAMENTO LIKE $like
                  AND ac.DATA_ABERTURA >= '$dataIni'
                  AND ac.DATA_ABERTURA <= '$dataFim'
                  AND ac.STATUS = 'F'
                ORDER BY ac.DATA_ABERTURA DESC, ac.COD_ABERT_CAIXA DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    // =========================================================================
    // FORMAS DE PAGAMENTO
    // =========================================================================

    /**
     * Busca NR_NOTAs do turno via COD_ABERT_CAIXA (FK indexada = rápido),
     * depois filtra por USUARIO em PHP (evita full-scan + garante precisão).
     */
    public function nrNotasDoTurno(int $codAbertCaixa, string $operador): array
    {
        $opUpper = strtoupper(trim($operador));
        try {
            // Primário: COD_ABERT_CAIXA identifica o caixa com precisão
            $rows = $this->pdo->query("
                SELECT NR_NOTA, TRIM(USUARIO) AS USUARIO
                FROM NOTAS_CAB
                WHERE COD_EMPRESA     = {$this->codEmpresa}
                  AND COD_ABERT_CAIXA = $codAbertCaixa
                  AND DATA_CANC IS NULL
                  AND COALESCE(TOTAL_NOTA, 0) > 0
            ")->fetchAll(\PDO::FETCH_ASSOC);

            if (!empty($rows)) {
                // Se encontrou notas pelo caixa, retorna todas (independente do USUARIO)
                // O COD_ABERT_CAIXA já garante que são deste caixa específico
                return array_column($rows, 'NR_NOTA');
            }

            // Fallback: se COD_ABERT_CAIXA não tem notas, tenta pelo USUARIO + data
            // (alguns sistemas não preenchem COD_ABERT_CAIXA em NOTAS_CAB)
            return [];
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * Valores APURADOS PELO SISTEMA por forma de pagamento.
     * DATA_EMISSAO usa IDX_FORMAS_NOTAS_EMPRESA_DATA → resultado pequeno → IN() é rápido.
     */
    public function formasPorVendas(array $nrNotas, string $dAb, string $dFe): array
    {
        if (empty($nrNotas)) return [];
        $in = implode(',', array_map('intval', $nrNotas));
        try {
            // UPPER unifica variantes de caps (ex.: "Pix" e "PIX" duplicados no ERP viram 1 linha)
            return $this->pdo->query("
                SELECT
                    COALESCE(UPPER(TRIM(fp.DESCRICAO)), 'NÃO IDENTIFICADO') AS DESCRICAO,
                    COALESCE(fp.TIPO, '0')                                   AS TIPO,
                    ''                                                        AS COD,
                    COALESCE(SUM(fn.VALOR), 0)                                AS ENTRADAS,
                    0                                                          AS SAIDAS
                FROM FORMAS_NOTAS fn
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                WHERE fn.COD_EMPRESA  = {$this->codEmpresa}
                  AND fn.DATA_EMISSAO BETWEEN '$dAb' AND '$dFe'
                  AND fn.NR_NOTA IN ($in)
                GROUP BY UPPER(TRIM(fp.DESCRICAO)), fp.TIPO
                ORDER BY fp.TIPO, UPPER(TRIM(fp.DESCRICAO))
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * @deprecated Mantido para compatibilidade — use formasPorVendas() no lugar.
     * Buscava de LANC_TESOURARIA que na maioria dos sistemas não tem todos os registros.
     */
    public function formasSistema(int $codAbertCaixa): array
    {
        return [];
    }

    /**
     * Valores INFORMADOS PELO OPERADOR por forma de pagamento.
     * Origem: FECHAMENTO_CAIXA_CONDICOES
     */
    public function formasOperador(int $codAbertCaixa): array
    {
        try {
            return $this->pdo->query("
                SELECT
                    TRIM(fc.COD_FORMA_PGTO)         AS COD,
                    COALESCE(TRIM(fp.DESCRICAO), TRIM(fc.COD_FORMA_PGTO)) AS DESCRICAO,
                    COALESCE(fp.TIPO, '0')           AS TIPO,
                    COALESCE(fc.VALOR_INFORMADO, 0)  AS VALOR_INFORMADO
                FROM FECHAMENTO_CAIXA_CONDICOES fc
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fc.COD_FORMA_PGTO
                WHERE fc.COD_ABERT_CAIXA = $codAbertCaixa
                  AND fc.COD_EMPRESA     = {$this->codEmpresa}
                ORDER BY fp.TIPO, fc.COD_FORMA_PGTO
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) {
            return [];
        }
    }

    // =========================================================================
    // SANGRIAS E SUPRIMENTOS (LANC_TESOURARIA)
    // =========================================================================

    /**
     * Sangrias do caixa: retiradas de dinheiro durante o turno.
     * SANGRIA_SUPRIM = 'S', DEB_CRE = 'D'
     */
    public function sangrias(int $codAbertCaixa): array
    {
        try {
            return $this->pdo->query("
                SELECT
                    SEQ_LANC,
                    DATA_HORA_LANC,
                    VALOR,
                    COALESCE(TRIM(HISTORICO), '') AS HISTORICO,
                    COALESCE(TRIM(USUARIO),   '') AS USUARIO
                FROM LANC_TESOURARIA
                WHERE COD_ABERT_CAIXA = $codAbertCaixa
                  AND COD_EMPRESA     = {$this->codEmpresa}
                  AND SANGRIA_SUPRIM  = 'S'
                ORDER BY DATA_HORA_LANC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * Suprimentos / reforços: entradas de dinheiro durante o turno.
     * SANGRIA_SUPRIM = 'E', DEB_CRE = 'C'
     */
    public function suprimentosReforcos(int $codAbertCaixa): array
    {
        try {
            return $this->pdo->query("
                SELECT
                    SEQ_LANC,
                    DATA_HORA_LANC,
                    VALOR,
                    COALESCE(TRIM(HISTORICO), '') AS HISTORICO,
                    COALESCE(TRIM(USUARIO),   '') AS USUARIO
                FROM LANC_TESOURARIA
                WHERE COD_ABERT_CAIXA = $codAbertCaixa
                  AND COD_EMPRESA     = {$this->codEmpresa}
                  AND SANGRIA_SUPRIM  = 'E'
                ORDER BY DATA_HORA_LANC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) {
            return [];
        }
    }

    // =========================================================================
    // VENDAS / RESUMO DO TURNO (NOTAS_CAB)
    // =========================================================================

    /**
     * Resumo de vendas do operador no período do turno:
     * quantidade de notas, total faturado e ticket médio.
     */
    public function resumoVendas(array $nrNotas): array
    {
        $vazio = ['QTD_NOTAS' => 0, 'TOTAL' => 0, 'TICKET_MEDIO' => 0, 'MAIOR_VENDA' => 0];
        if (empty($nrNotas)) return $vazio;
        $in = implode(',', array_map('intval', $nrNotas));
        try {
            $row = $this->pdo->query("
                SELECT
                    COUNT(*)                     AS QTD_NOTAS,
                    COALESCE(SUM(TOTAL_NOTA), 0) AS TOTAL,
                    COALESCE(AVG(TOTAL_NOTA), 0) AS TICKET_MEDIO,
                    COALESCE(MAX(TOTAL_NOTA), 0) AS MAIOR_VENDA
                FROM NOTAS_CAB
                WHERE COD_EMPRESA = {$this->codEmpresa}
                  AND NR_NOTA IN ($in)
                  AND TOTAL_NOTA > 0
            ")->fetch(\PDO::FETCH_ASSOC);
            return $row ?: $vazio;
        } catch (\Throwable) {
            return $vazio;
        }
    }

    /**
     * Cancelamentos do operador no período do turno.
     * Retorna nota, data/hora, total e motivo (DATA_CANC preenchido).
     */
    public function cancelamentos(int $codAbertCaixa, string $operador): array
    {
        $opUpper = strtoupper(trim($operador));
        try {
            $rows = $this->pdo->query("
                SELECT
                    nc.NR_NOTA,
                    nc.DATA_EMISSAO,
                    nc.DATA_CANC,
                    COALESCE(nc.TOTAL_NOTA, 0)     AS TOTAL_NOTA,
                    COALESCE(TRIM(nc.USUARIO), '')  AS USUARIO,
                    COALESCE(TRIM(nc.OBS), '')      AS OBS
                FROM NOTAS_CAB nc
                WHERE nc.COD_EMPRESA     = {$this->codEmpresa}
                  AND nc.COD_ABERT_CAIXA = $codAbertCaixa
                  AND nc.DATA_CANC IS NOT NULL
                ORDER BY nc.DATA_CANC DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
            return array_filter($rows, fn($r) => strtoupper(trim($r['USUARIO'])) === $opUpper);
        } catch (\Throwable) {
            return [];
        }
    }

    /**
     * Vendas do operador no período com indicação de documento fiscal.
     * Detecta se existe SAT, NFC-e ou ECF vinculado à venda.
     * Possui fallback silencioso caso colunas opcionais não existam.
     */
    /**
     * Retorna vendas com indicação fiscal usando NR_NOTAs já carregados.
     * DATA_EMISSAO em FORMAS_NOTAS usa IDX_FORMAS_NOTAS_EMPRESA_DATA → rápido.
     */
    public function vendasComSemNota(array $nrNotas, string $dAb, string $dFe): array
    {
        if (empty($nrNotas)) return [];
        $in = implode(',', array_map('intval', $nrNotas));

        // ── 1. Vendas do caixa (NOTAS_CAB) ──────────────────────────────────
        try {
            $vendas = $this->pdo->query("
                SELECT FIRST 500
                    NR_NOTA                                           AS NR_VENDA,
                    DATA_EMISSAO,
                    COALESCE(TOTAL_NOTA, 0)                           AS TOTAL,
                    COALESCE(TRIM(USUARIO), '')                       AS OPERADOR,
                    COALESCE(TRIM(NOME_CONSUMIDOR), 'Consumidor')     AS CLIENTE,
                    COALESCE(TRIM(NFE), '')                           AS NFE_FLAG,
                    COALESCE(TRIM(SERIE), '')                         AS SERIE
                FROM NOTAS_CAB
                WHERE COD_EMPRESA = {$this->codEmpresa}
                  AND NR_NOTA IN ($in)
                ORDER BY DATA_EMISSAO DESC, NR_NOTA DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) {
            return [];
        }

        if (empty($vendas)) return [];

        // ── 2. Detecta NFC-e pelo campo SERIE já presente em NOTAS_CAB ─────────
        // SERIE='SC' = NFC-e neste banco (confirmado via NFCE_CABN.SERIE='SC').
        // Evita query extra em tabela de 1,17M linhas — sem perda de precisão.
        foreach ($vendas as &$v) {
            $serie = strtoupper(trim((string)($v['SERIE'] ?? '')));
            $v['TEM_NFCE']       = ($serie === 'SC') ? '1' : '0';
            $v['PROTOCOLO_NFCE'] = '';   // protocolo não carregado (performance)
        }
        unset($v);

        // ── 3. Forma + valor real pago (FORMAS_NOTAS) — IDX por DATA_EMISSAO ──
        // Usar SUM(VALOR) de FORMAS_NOTAS como total fiscal:
        // - Exclui automaticamente notas B2B/internas sem registro de pagamento
        // - Garante que o total fiscal bate com as formas de pagamento do caixa
        $mapFormas = [];
        try {
            $formasRows = $this->pdo->query("
                SELECT fn.NR_NOTA,
                       MIN(TRIM(fp.DESCRICAO))  AS FORMA_PGTO,
                       SUM(fn.VALOR)             AS VALOR_PAGO
                FROM FORMAS_NOTAS fn
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                WHERE fn.COD_EMPRESA  = {$this->codEmpresa}
                  AND fn.DATA_EMISSAO BETWEEN '$dAb' AND '$dFe'
                  AND fn.NR_NOTA IN ($in)
                GROUP BY fn.NR_NOTA
            ")->fetchAll(\PDO::FETCH_ASSOC);
            foreach ($formasRows as $fr) {
                $mapFormas[(int)$fr['NR_NOTA']] = [
                    'forma' => $fr['FORMA_PGTO'] ?? 'N/A',
                    'valor' => round((float)$fr['VALOR_PAGO'], 2),
                ];
            }
        } catch (\Throwable) {}

        // ── 4. Monta resultado: só notas com pagamento registrado ────────────
        // Notas sem FORMAS_NOTAS (B2B, internas, etc.) ficam de fora do fiscal.
        $result = [];
        foreach ($vendas as $v) {
            $nrv = (int)$v['NR_VENDA'];
            if (!isset($mapFormas[$nrv])) continue; // sem pagamento = fora do caixa
            $v['FORMA_PGTO'] = $mapFormas[$nrv]['forma'];
            $v['TOTAL']      = $mapFormas[$nrv]['valor']; // valor real pago
            $result[] = $v;
        }

        return $result;
    }

    /**
     * Devoluções do operador no período (NOTAS_CAB com TIPO_NOTA = 'D').
     * Se a coluna TIPO_NOTA não existir, retorna array vazio silenciosamente.
     */
    public function devolucoes(int $codAbertCaixa, string $operador): array
    {
        $opUpper = strtoupper(trim($operador));
        try {
            $rows = $this->pdo->query("
                SELECT
                    nc.NR_NOTA,
                    nc.DATA_EMISSAO,
                    COALESCE(nc.TOTAL_NOTA, 0)           AS TOTAL_NOTA,
                    COALESCE(TRIM(nc.USUARIO), '')        AS USUARIO,
                    COALESCE(TRIM(nc.OBS), '')            AS OBS,
                    COALESCE(TRIM(nc.NR_NOTA_ORIGEM), '') AS NR_NOTA_ORIGEM
                FROM NOTAS_CAB nc
                WHERE nc.COD_EMPRESA     = {$this->codEmpresa}
                  AND nc.COD_ABERT_CAIXA = $codAbertCaixa
                  AND nc.DATA_CANC IS NULL
                  AND nc.TIPO_NOTA = 'D'
                ORDER BY nc.DATA_EMISSAO DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
            return array_filter($rows, fn($r) => strtoupper(trim($r['USUARIO'])) === $opUpper);
        } catch (\Throwable) {
            return [];
        }
    }

    // =========================================================================
    // BATCH QUERIES — carregam dados de múltiplos caixas em 1 única query
    // Reduz de 10×N queries para ~10 queries fixas independente do volume
    // =========================================================================

    /**
     * Retorna mapa [codAbertCaixa => [nrNotas]] para vários caixas de uma vez.
     */
    public function nrNotasBatch(array $caixaIds): array
    {
        if (empty($caixaIds)) return [];
        $in    = implode(',', array_map('intval', $caixaIds));
        $chave = 'fc_nr_notas_' . md5($in . $this->codEmpresa);
        try {
            $rows = Cache::lembrar($chave, 300, function () use ($in) {
                return $this->pdo->query("
                    SELECT COD_ABERT_CAIXA, NR_NOTA
                    FROM NOTAS_CAB
                    WHERE COD_EMPRESA     = {$this->codEmpresa}
                      AND COD_ABERT_CAIXA IN ($in)
                      AND DATA_CANC IS NULL
                      AND COALESCE(TOTAL_NOTA, 0) > 0
                ")->fetchAll(\PDO::FETCH_ASSOC);
            });
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) {
            $cid = (int)$r['COD_ABERT_CAIXA'];
            $mapa[$cid][] = (int)$r['NR_NOTA'];
        }
        return $mapa;
    }

    /**
     * Formas de pagamento (sistema) para múltiplas notas em 1 query.
     * Retorna mapa [caixaId => [rows]] usando o mapa nrNotas→caixa fornecido.
     */
    public function formasPorVendasBatch(array $notaCaixaMap, string $dAb, string $dFe): array
    {
        if (empty($notaCaixaMap)) return [];
        $allNotas = array_keys($notaCaixaMap);
        $in = implode(',', array_map('intval', $allNotas));
        try {
            $rows = $this->pdo->query("
                SELECT
                    fn.NR_NOTA,
                    COALESCE(UPPER(TRIM(fp.DESCRICAO)), 'NÃO IDENTIFICADO') AS DESCRICAO,
                    COALESCE(fp.TIPO, '0')                                   AS TIPO,
                    ''                                                        AS COD,
                    COALESCE(SUM(fn.VALOR), 0)                                AS ENTRADAS,
                    0                                                          AS SAIDAS
                FROM FORMAS_NOTAS fn
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                WHERE fn.COD_EMPRESA  = {$this->codEmpresa}
                  AND fn.DATA_EMISSAO BETWEEN '$dAb' AND '$dFe'
                  AND fn.NR_NOTA IN ($in)
                GROUP BY fn.NR_NOTA, UPPER(TRIM(fp.DESCRICAO)), fp.TIPO
                ORDER BY fp.TIPO, UPPER(TRIM(fp.DESCRICAO))
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) {
            $nr  = (int)$r['NR_NOTA'];
            $cid = $notaCaixaMap[$nr] ?? null;
            if ($cid === null) continue;
            $mapa[$cid][] = $r;
        }
        return $mapa;
    }

    /**
     * Formas informadas pelo operador para múltiplos caixas em 1 query.
     */
    public function formasOperadorBatch(array $caixaIds): array
    {
        if (empty($caixaIds)) return [];
        $in = implode(',', array_map('intval', $caixaIds));
        try {
            $rows = $this->pdo->query("
                SELECT
                    fc.COD_ABERT_CAIXA,
                    TRIM(fc.COD_FORMA_PGTO)         AS COD,
                    COALESCE(TRIM(fp.DESCRICAO), TRIM(fc.COD_FORMA_PGTO)) AS DESCRICAO,
                    COALESCE(fp.TIPO, '0')           AS TIPO,
                    COALESCE(fc.VALOR_INFORMADO, 0)  AS VALOR_INFORMADO
                FROM FECHAMENTO_CAIXA_CONDICOES fc
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fc.COD_FORMA_PGTO
                WHERE fc.COD_ABERT_CAIXA IN ($in)
                  AND fc.COD_EMPRESA     = {$this->codEmpresa}
                ORDER BY fc.COD_ABERT_CAIXA, fp.TIPO, fc.COD_FORMA_PGTO
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) {
            $mapa[(int)$r['COD_ABERT_CAIXA']][] = $r;
        }
        return $mapa;
    }

    /**
     * Sangrias para múltiplos caixas em 1 query.
     */
    public function sangriasBatch(array $caixaIds): array
    {
        if (empty($caixaIds)) return [];
        $in = implode(',', array_map('intval', $caixaIds));
        try {
            $rows = $this->pdo->query("
                SELECT
                    COD_ABERT_CAIXA,
                    SEQ_LANC,
                    DATA_HORA_LANC,
                    VALOR,
                    COALESCE(TRIM(HISTORICO), '') AS HISTORICO,
                    COALESCE(TRIM(USUARIO),   '') AS USUARIO
                FROM LANC_TESOURARIA
                WHERE COD_ABERT_CAIXA IN ($in)
                  AND COD_EMPRESA     = {$this->codEmpresa}
                  AND SANGRIA_SUPRIM  = 'S'
                ORDER BY COD_ABERT_CAIXA, DATA_HORA_LANC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) { $mapa[(int)$r['COD_ABERT_CAIXA']][] = $r; }
        return $mapa;
    }

    /**
     * Suprimentos/reforços para múltiplos caixas em 1 query.
     */
    public function suprimentosBatch(array $caixaIds): array
    {
        if (empty($caixaIds)) return [];
        $in = implode(',', array_map('intval', $caixaIds));
        try {
            $rows = $this->pdo->query("
                SELECT
                    COD_ABERT_CAIXA,
                    SEQ_LANC,
                    DATA_HORA_LANC,
                    VALOR,
                    COALESCE(TRIM(HISTORICO), '') AS HISTORICO,
                    COALESCE(TRIM(USUARIO),   '') AS USUARIO
                FROM LANC_TESOURARIA
                WHERE COD_ABERT_CAIXA IN ($in)
                  AND COD_EMPRESA     = {$this->codEmpresa}
                  AND SANGRIA_SUPRIM  = 'E'
                ORDER BY COD_ABERT_CAIXA, DATA_HORA_LANC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) { $mapa[(int)$r['COD_ABERT_CAIXA']][] = $r; }
        return $mapa;
    }

    /**
     * Cancelamentos para múltiplos caixas em 1 query.
     */
    public function cancelamentosBatch(array $caixaIds): array
    {
        if (empty($caixaIds)) return [];
        $in = implode(',', array_map('intval', $caixaIds));
        try {
            $rows = $this->pdo->query("
                SELECT
                    nc.COD_ABERT_CAIXA,
                    nc.NR_NOTA,
                    nc.DATA_EMISSAO,
                    nc.DATA_CANC,
                    COALESCE(nc.TOTAL_NOTA, 0)     AS TOTAL_NOTA,
                    COALESCE(TRIM(nc.USUARIO), '')  AS USUARIO,
                    COALESCE(TRIM(nc.OBS), '')      AS OBS
                FROM NOTAS_CAB nc
                WHERE nc.COD_EMPRESA     = {$this->codEmpresa}
                  AND nc.COD_ABERT_CAIXA IN ($in)
                  AND nc.DATA_CANC IS NOT NULL
                ORDER BY nc.COD_ABERT_CAIXA, nc.DATA_CANC DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) { $mapa[(int)$r['COD_ABERT_CAIXA']][] = $r; }
        return $mapa;
    }

    /**
     * Devoluções para múltiplos caixas em 1 query.
     */
    public function devolucoesBatch(array $caixaIds): array
    {
        if (empty($caixaIds)) return [];
        $in = implode(',', array_map('intval', $caixaIds));
        try {
            $rows = $this->pdo->query("
                SELECT
                    nc.COD_ABERT_CAIXA,
                    nc.NR_NOTA,
                    nc.DATA_EMISSAO,
                    COALESCE(nc.TOTAL_NOTA, 0)           AS TOTAL_NOTA,
                    COALESCE(TRIM(nc.USUARIO), '')        AS USUARIO,
                    COALESCE(TRIM(nc.OBS), '')            AS OBS,
                    COALESCE(TRIM(nc.NR_NOTA_ORIGEM), '') AS NR_NOTA_ORIGEM
                FROM NOTAS_CAB nc
                WHERE nc.COD_EMPRESA     = {$this->codEmpresa}
                  AND nc.COD_ABERT_CAIXA IN ($in)
                  AND nc.DATA_CANC IS NULL
                  AND nc.TIPO_NOTA = 'D'
                ORDER BY nc.COD_ABERT_CAIXA, nc.DATA_EMISSAO DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) { $mapa[(int)$r['COD_ABERT_CAIXA']][] = $r; }
        return $mapa;
    }

    /**
     * Resumo de vendas (qtd, total, ticket, maior) para múltiplas notas em 1 query.
     * Retorna mapa [caixaId => resumo] usando o mapa nota→caixa.
     */
    public function resumoVendasBatch(array $notaCaixaMap): array
    {
        if (empty($notaCaixaMap)) return [];
        $allNotas = array_keys($notaCaixaMap);
        $in = implode(',', array_map('intval', $allNotas));

        // Primeiro: precisamos agrupar por caixa. Como NOTAS_CAB tem COD_ABERT_CAIXA,
        // usamos ele diretamente para agrupar no banco.
        // Extraímos os caixaIds únicos do mapa
        $caixaIds = array_unique(array_values($notaCaixaMap));
        $inCaixas = implode(',', array_map('intval', $caixaIds));

        try {
            $rows = $this->pdo->query("
                SELECT
                    COD_ABERT_CAIXA,
                    COUNT(*)                     AS QTD_NOTAS,
                    COALESCE(SUM(TOTAL_NOTA), 0) AS TOTAL,
                    COALESCE(AVG(TOTAL_NOTA), 0) AS TICKET_MEDIO,
                    COALESCE(MAX(TOTAL_NOTA), 0) AS MAIOR_VENDA
                FROM NOTAS_CAB
                WHERE COD_EMPRESA     = {$this->codEmpresa}
                  AND COD_ABERT_CAIXA IN ($inCaixas)
                  AND NR_NOTA IN ($in)
                  AND DATA_CANC IS NULL
                  AND TOTAL_NOTA > 0
                GROUP BY COD_ABERT_CAIXA
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        $mapa = [];
        foreach ($rows as $r) {
            $mapa[(int)$r['COD_ABERT_CAIXA']] = $r;
        }
        return $mapa;
    }

    /**
     * Vendas com classificação fiscal para múltiplas notas em 2 queries batch.
     * Retorna mapa [caixaId => [vendas]].
     */
    public function vendasFiscalBatch(array $notaCaixaMap, string $dAb, string $dFe): array
    {
        if (empty($notaCaixaMap)) return [];
        $allNotas = array_keys($notaCaixaMap);
        $in = implode(',', array_map('intval', $allNotas));

        // Query 1: dados das notas (NOTAS_CAB)
        try {
            $vendas = $this->pdo->query("
                SELECT FIRST 2000
                    NR_NOTA                                           AS NR_VENDA,
                    COD_ABERT_CAIXA,
                    DATA_EMISSAO,
                    COALESCE(TOTAL_NOTA, 0)                           AS TOTAL,
                    COALESCE(TRIM(USUARIO), '')                       AS OPERADOR,
                    COALESCE(TRIM(NOME_CONSUMIDOR), 'Consumidor')     AS CLIENTE,
                    COALESCE(TRIM(NFE), '')                           AS NFE_FLAG,
                    COALESCE(TRIM(SERIE), '')                         AS SERIE
                FROM NOTAS_CAB
                WHERE COD_EMPRESA = {$this->codEmpresa}
                  AND NR_NOTA IN ($in)
                ORDER BY COD_ABERT_CAIXA, DATA_EMISSAO DESC, NR_NOTA DESC
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { return []; }

        if (empty($vendas)) return [];

        // Detecta NFC-e por SERIE (sem query extra)
        foreach ($vendas as &$v) {
            $serie = strtoupper(trim((string)($v['SERIE'] ?? '')));
            $v['TEM_NFCE']       = ($serie === 'SC') ? '1' : '0';
            $v['PROTOCOLO_NFCE'] = '';
        }
        unset($v);

        // Query 2: formas de pagamento (FORMAS_NOTAS) para saber valor real e forma
        try {
            $formasRows = $this->pdo->query("
                SELECT fn.NR_NOTA,
                       MIN(TRIM(fp.DESCRICAO))  AS FORMA_PGTO,
                       SUM(fn.VALOR)             AS VALOR_PAGO
                FROM FORMAS_NOTAS fn
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                WHERE fn.COD_EMPRESA  = {$this->codEmpresa}
                  AND fn.DATA_EMISSAO BETWEEN '$dAb' AND '$dFe'
                  AND fn.NR_NOTA IN ($in)
                GROUP BY fn.NR_NOTA
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) { $formasRows = []; }

        $mapFormas = [];
        foreach ($formasRows as $fr) {
            $mapFormas[(int)$fr['NR_NOTA']] = [
                'forma' => $fr['FORMA_PGTO'] ?? 'N/A',
                'valor' => round((float)$fr['VALOR_PAGO'], 2),
            ];
        }

        // Monta resultado agrupado por caixa, só notas com pagamento
        $mapa = [];
        foreach ($vendas as $v) {
            $nr  = (int)$v['NR_VENDA'];
            $cid = (int)($v['COD_ABERT_CAIXA'] ?? ($notaCaixaMap[$nr] ?? 0));
            if (!isset($mapFormas[$nr])) continue; // sem pagamento = B2B/interna
            $v['FORMA_PGTO'] = $mapFormas[$nr]['forma'];
            $v['TOTAL']      = $mapFormas[$nr]['valor'];
            $mapa[$cid][]    = $v;
        }
        return $mapa;
    }

    /**
     * Operadores que tiveram fechamentos no período, com totais.
     * Ordenado por TOTAL_INFORMADO DESC para mostrar os mais relevantes primeiro.
     */
    public function operadoresComMovimento(string $dataIni, string $dataFim): array
    {
        $ttl   = ($dataFim >= date('Y-m-d')) ? 300 : 86400;
        $chave = "fc_operadores_{$this->codEmpresa}_{$dataIni}_{$dataFim}";
        return Cache::lembrar($chave, $ttl, function () use ($dataIni, $dataFim) {
            try {
                return $this->pdo->query("
                    SELECT
                        TRIM(ac.COD_USUARIO_FECHAMENTO)      AS OPERADOR,
                        COUNT(*)                              AS QTD_FECHAMENTOS,
                        COALESCE(SUM(ac.VALOR_INFORMADO), 0) AS TOTAL_INFORMADO,
                        MIN(ac.DATA_ABERTURA)                 AS PRIMEIRA_DATA,
                        MAX(ac.DATA_ABERTURA)                 AS ULTIMA_DATA
                    FROM ABERTURA_CAIXA ac
                    WHERE ac.COD_EMPRESA = {$this->codEmpresa}
                      AND ac.STATUS = 'F'
                      AND ac.DATA_ABERTURA BETWEEN '$dataIni' AND '$dataFim'
                      AND ac.COD_USUARIO_FECHAMENTO IS NOT NULL
                      AND TRIM(ac.COD_USUARIO_FECHAMENTO) <> ''
                    GROUP BY ac.COD_USUARIO_FECHAMENTO
                    ORDER BY SUM(ac.VALOR_INFORMADO) DESC
                ")->fetchAll(\PDO::FETCH_ASSOC);
            } catch (\Throwable) {
                return [];
            }
        });
    }

    /**
     * Totais de vendas por forma de pagamento no período (NOTAS_CAB → FORMAS_NOTAS).
     * Usado para o resumo geral no topo da página.
     */
    public function vendasNoPeriodo(string $operador, string $dataIni, string $dataFim): array
    {
        $op = $this->pdo->quote(trim($operador));
        try {
            return $this->pdo->query("
                SELECT
                    TRIM(fn.COD_FORMA_PGTO)        AS COD,
                    TRIM(fp.DESCRICAO)              AS DESCRICAO,
                    fp.TIPO,
                    SUM(fn.VALOR_TOTAL)             AS VALOR,
                    COUNT(DISTINCT nc.NR_NOTA)      AS QTD_NOTAS
                FROM NOTAS_CAB nc
                JOIN FORMAS_NOTAS fn ON fn.NR_NOTA     = nc.NR_NOTA
                                    AND fn.COD_EMPRESA  = nc.COD_EMPRESA
                LEFT JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                WHERE nc.COD_EMPRESA = {$this->codEmpresa}
                  AND UPPER(TRIM(nc.USUARIO)) = UPPER($op)
                  AND nc.DATA_EMISSAO BETWEEN '$dataIni' AND '$dataFim'
                  AND nc.DATA_CANC IS NULL
                GROUP BY fn.COD_FORMA_PGTO, fp.DESCRICAO, fp.TIPO
                ORDER BY fp.TIPO, fn.COD_FORMA_PGTO
            ")->fetchAll(\PDO::FETCH_ASSOC);
        } catch (\Throwable) {
            return [];
        }
    }
}
