<?php

namespace App\Repositories;

use App\Core\Cache;

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

    public function kpis(): array
    {
        // Cache 5 min — query pesada (RECEBER_PAGAR + JOIN)
        // BETWEEN em VENCIMENTO usa índice: evita full scan de toda a tabela
        return Cache::lembrar('fin_kpis', 3600, function () {
            $passado = date('Y-m-d', strtotime('-5 years'));   // inadimplência últimos 5 anos
            $futuro  = date('Y-m-d', strtotime('+3 years'));   // a receber/pagar próximos 3 anos
            $sql = "SELECT
                SUM(CASE WHEN t.TIPO_NATUREZA = 'V' AND rp.SALDO_TITULO > 0 THEN rp.SALDO_TITULO ELSE 0 END) A_RECEBER,
                SUM(CASE WHEN t.TIPO_NATUREZA = 'C' AND rp.SALDO_TITULO > 0 THEN rp.SALDO_TITULO ELSE 0 END) A_PAGAR,
                SUM(CASE WHEN t.TIPO_NATUREZA = 'V' AND rp.VENCIMENTO < CURRENT_DATE AND rp.SALDO_TITULO > 0 THEN rp.SALDO_TITULO ELSE 0 END) VENCIDOS_RECEBER,
                SUM(CASE WHEN t.TIPO_NATUREZA = 'C' AND rp.VENCIMENTO < CURRENT_DATE AND rp.SALDO_TITULO > 0 THEN rp.SALDO_TITULO ELSE 0 END) VENCIDOS_PAGAR,
                COUNT(CASE WHEN t.TIPO_NATUREZA = 'V' AND rp.SALDO_TITULO > 0 THEN 1 END) QTD_RECEBER,
                COUNT(CASE WHEN t.TIPO_NATUREZA = 'C' AND rp.SALDO_TITULO > 0 THEN 1 END) QTD_PAGAR
            FROM RECEBER_PAGAR rp
            JOIN TIPO_OPERACAO t ON rp.COD_TIPO_OPERACAO = t.COD_TIPO_OPERACAO
            WHERE rp.VENCIMENTO BETWEEN '{$passado}' AND '{$futuro}'";
            return $this->pdo->query($sql)->fetch(\PDO::FETCH_ASSOC) ?: [];
        });
    }

    public function contasReceber(string $dataIni, string $dataFim, string $situacao = 'aberto'): array
    {
        $filtroSaldo = $situacao === 'aberto' ? 'AND rp.SALDO_TITULO > 0' : '';
        $sql = "SELECT FIRST 300
            rp.PARCELA, rp.NR_NOTA, rp.SERIE,
            rp.DATA_EMISSAO, rp.VENCIMENTO,
            rp.VALOR_TITULO, rp.SALDO_TITULO,
            (rp.VALOR_TITULO - rp.SALDO_TITULO) PAGO,
            p.NOME CLIENTE,
            rp.HISTORICO,
            CASE WHEN rp.VENCIMENTO < CURRENT_DATE AND rp.SALDO_TITULO > 0 THEN 'S' ELSE 'N' END VENCIDO
        FROM RECEBER_PAGAR rp
        JOIN TIPO_OPERACAO t ON rp.COD_TIPO_OPERACAO = t.COD_TIPO_OPERACAO
        LEFT JOIN PESSOAS p ON rp.COD_PESSOA = p.COD_PESSOA
        WHERE t.TIPO_NATUREZA = 'V'
          AND rp.VENCIMENTO BETWEEN ? AND ?
          {$filtroSaldo}
        ORDER BY rp.VENCIMENTO DESC";
        $st = $this->pdo->prepare($sql);
        $st->execute([$dataIni, $dataFim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function contasPagar(string $dataIni, string $dataFim, string $situacao = 'aberto'): array
    {
        $filtroSaldo = $situacao === 'aberto' ? 'AND rp.SALDO_TITULO > 0' : '';
        $sql = "SELECT FIRST 300
            rp.PARCELA, rp.NR_NOTA, rp.SERIE,
            rp.DATA_EMISSAO, rp.VENCIMENTO,
            rp.VALOR_TITULO, rp.SALDO_TITULO,
            (rp.VALOR_TITULO - rp.SALDO_TITULO) PAGO,
            p.NOME FORNECEDOR,
            rp.HISTORICO,
            CASE WHEN rp.VENCIMENTO < CURRENT_DATE AND rp.SALDO_TITULO > 0 THEN 'S' ELSE 'N' END VENCIDO
        FROM RECEBER_PAGAR rp
        JOIN TIPO_OPERACAO t ON rp.COD_TIPO_OPERACAO = t.COD_TIPO_OPERACAO
        LEFT JOIN PESSOAS p ON rp.COD_PESSOA = p.COD_PESSOA
        WHERE t.TIPO_NATUREZA = 'C'
          AND rp.VENCIMENTO BETWEEN ? AND ?
          {$filtroSaldo}
        ORDER BY rp.VENCIMENTO";
        $st = $this->pdo->prepare($sql);
        $st->execute([$dataIni, $dataFim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function fluxoCaixaMensal(int $ano): array
    {
        $ini = "{$ano}-01-01"; $fim = "{$ano}-12-31";
        $sql = "SELECT
            EXTRACT(MONTH FROM rp.VENCIMENTO) MES,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'V' THEN rp.VALOR_TITULO ELSE 0 END) PREVISTO_RECEBER,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'C' THEN rp.VALOR_TITULO ELSE 0 END) PREVISTO_PAGAR,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'V' THEN (rp.VALOR_TITULO - rp.SALDO_TITULO) ELSE 0 END) REALIZADO_RECEBER,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'C' THEN (rp.VALOR_TITULO - rp.SALDO_TITULO) ELSE 0 END) REALIZADO_PAGAR
        FROM RECEBER_PAGAR rp
        JOIN TIPO_OPERACAO t ON rp.COD_TIPO_OPERACAO = t.COD_TIPO_OPERACAO
        WHERE rp.VENCIMENTO BETWEEN ? AND ?
        GROUP BY EXTRACT(MONTH FROM rp.VENCIMENTO)
        ORDER BY MES";
        $st = $this->pdo->prepare($sql);
        $st->execute([$ini, $fim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function fluxoCaixaDiario(string $dataIni, string $dataFim): array
    {
        $sql = "SELECT
            rp.VENCIMENTO DATA,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'V' THEN rp.VALOR_TITULO ELSE 0 END) ENTRADAS,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'C' THEN rp.VALOR_TITULO ELSE 0 END) SAIDAS
        FROM RECEBER_PAGAR rp
        JOIN TIPO_OPERACAO t ON rp.COD_TIPO_OPERACAO = t.COD_TIPO_OPERACAO
        WHERE rp.VENCIMENTO BETWEEN ? AND ?
        GROUP BY rp.VENCIMENTO
        ORDER BY rp.VENCIMENTO";
        $st = $this->pdo->prepare($sql);
        $st->execute([$dataIni, $dataFim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function dreMensal(int $ano): array
    {
        // Filtro de empresa (lê config/database.php sem criar dependência circular)
        $codEmpresa = null;
        try {
            $codEmpresa = \App\Core\Lojas::codEmpresaAtiva() ?: null;
        } catch (\Throwable) {}

        $filtroEmp = $codEmpresa ? "AND n.COD_EMPRESA = {$codEmpresa}" : '';

        // ── Override tipos_venda_extras / tipos_venda_excluir do config/database.php ──
        // Real Prime tem op 115 (MOV_INTERNA='S') que precisa CONTAR como venda; antes ficava de fora.
        $cfg = [];
        try {
            $cfgPath = dirname(__DIR__, 2) . '/config/database.php';
            if (file_exists($cfgPath)) $cfg = require $cfgPath;
        } catch (\Throwable) {}
        $extras  = array_map('intval', (array)($cfg['tipos_venda_extras']  ?? []));
        $excluir = array_map('intval', (array)($cfg['tipos_venda_excluir'] ?? []));

        // Condição "é venda válida": flags padrão OU operação na lista extras; exclui as forçadas
        $vendaCond = "(t.TIPO_NATUREZA = 'V'
                      AND (t.DEVOLUCAO = 'N' OR t.DEVOLUCAO IS NULL)
                      AND (t.ETRANSF   = 'N' OR t.ETRANSF   IS NULL)
                      AND (t.MOV_INTERNA = 'N' OR t.MOV_INTERNA IS NULL))";
        if (!empty($extras))  $vendaCond = "({$vendaCond} OR t.COD_TIPO_OPERACAO IN (" . implode(',', $extras) . "))";
        if (!empty($excluir)) $vendaCond = "({$vendaCond} AND t.COD_TIPO_OPERACAO NOT IN (" . implode(',', $excluir) . "))";

        $sql = "SELECT
            EXTRACT(MONTH FROM n.DATA_EMISSAO) MES,
            SUM(CASE WHEN {$vendaCond} THEN n.TOTAL_NOTA ELSE 0 END) RECEITA_BRUTA,
            SUM(CASE WHEN {$vendaCond} THEN n.DESCONTO   ELSE 0 END) DESCONTOS,
            SUM(CASE WHEN t.TIPO_NATUREZA = 'C'
                      AND (t.DEVOLUCAO = 'N' OR t.DEVOLUCAO IS NULL)
                 THEN n.TOTAL_NOTA ELSE 0 END) CMV
        FROM NOTAS_CAB n
        JOIN TIPO_OPERACAO t ON n.COD_TIPO_OPERACAO = t.COD_TIPO_OPERACAO
        WHERE n.DATA_EMISSAO BETWEEN '{$ano}-01-01' AND '{$ano}-12-31'
          AND n.COD_SITUACAO <> 3
          AND n.DATA_CANC IS NULL
          {$filtroEmp}
        GROUP BY EXTRACT(MONTH FROM n.DATA_EMISSAO)
        ORDER BY MES";
        // $ano é int — interpolação segura contra SQL injection
        $st = $this->pdo->prepare($sql);
        $st->execute([]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function caixaMovimentos(string $dataIni, string $dataFim): array
    {
        $sql = "SELECT FIRST 200
            m.DATA, m.TURNO,
            m.QUEMABRIU, m.QUEMFECHOU,
            m.DATAHORAAB, m.DATAHORAFECH,
            m.STATUS
        FROM MOVIMENTO m
        WHERE m.DATA BETWEEN ? AND ?
        ORDER BY m.DATA DESC, m.DATAHORAAB DESC";
        $st = $this->pdo->prepare($sql);
        $st->execute([$dataIni, $dataFim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    /**
     * Detalhe de UM turno de caixa: formas que entraram, qtd de notas e total.
     * Filtro por data + faixa de hora (turno que vira a madrugada é tratado).
     * Read-only.
     */
    public function caixaDetalhe(string $dataAbertura, string $horaAbertura, ?string $dataFechamento, ?string $horaFechamento, ?int $codEmpresa = null): array
    {
        $key = "fin_caixa_det_{$dataAbertura}_{$horaAbertura}_{$dataFechamento}_{$horaFechamento}_{$codEmpresa}";
        return Cache::lembrar($key, 600, function () use ($dataAbertura, $horaAbertura, $dataFechamento, $horaFechamento, $codEmpresa) {
            try {
                $wEmp = $codEmpresa ? "AND nc.COD_EMPRESA = {$codEmpresa}" : '';

                // Usa DATA_HORA_VENDA (TIMESTAMP) — mais robusto que comparar HORA_EMISSAO como string.
                // Janela = [abertura, fechamento]. Se caixa não fechou, fim = abertura + 24h.
                $tsIni = "{$dataAbertura} {$horaAbertura}";
                if ($dataFechamento && $horaFechamento) {
                    $tsFim = "{$dataFechamento} {$horaFechamento}";
                } else {
                    $tsFim = date('Y-m-d H:i:s', strtotime("{$tsIni} +24 hours"));
                }
                $w = "nc.DATA_HORA_VENDA BETWEEN CAST('{$tsIni}' AS TIMESTAMP)
                                            AND CAST('{$tsFim}' AS TIMESTAMP)";

                // Formas do turno
                $sqlF = "SELECT
                    UPPER(TRIM(fp.DESCRICAO)) FORMA,
                    COUNT(*) QTD,
                    SUM(fn.VALOR) VALOR
                FROM FORMAS_NOTAS fn
                JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                JOIN NOTAS_CAB nc ON nc.NR_NOTA = fn.NR_NOTA
                                 AND nc.SERIE = fn.SERIE
                                 AND nc.COD_EMPRESA = fn.COD_EMPRESA
                                 AND nc.COD_TIPO_OPERACAO = fn.COD_TIPO_OPERACAO
                WHERE {$w}
                  AND nc.DATA_CANC IS NULL
                  {$wEmp}
                GROUP BY UPPER(TRIM(fp.DESCRICAO))
                ORDER BY 3 DESC";
                $formas = $this->pdo->query($sqlF)->fetchAll(\PDO::FETCH_ASSOC);

                // Totais
                $totalNotas = 0; $totalValor = 0.0; $cortesia = 0.0;
                foreach ($formas as $f) {
                    $totalNotas += (int)$f['QTD'];
                    $totalValor += (float)$f['VALOR'];
                    if ($f['FORMA'] === 'CORTESIA') $cortesia += (float)$f['VALOR'];
                }

                // # notas distintas no turno + faturamento bruto (TOTAL_NOTA = igual ao da Gestão de Vendas)
                $sqlN = "SELECT
                    COUNT(DISTINCT nc.NR_NOTA||'/'||nc.SERIE) NOTAS,
                    COALESCE(SUM(nc.TOTAL_NOTA), 0) FATURAMENTO
                    FROM NOTAS_CAB nc
                    WHERE {$w} AND nc.DATA_CANC IS NULL {$wEmp}";
                $row = $this->pdo->query($sqlN)->fetch(\PDO::FETCH_ASSOC);
                $qtdNotas    = (int)($row['NOTAS'] ?? 0);
                $faturamento = (float)($row['FATURAMENTO'] ?? 0);

                return [
                    'formas'      => $formas,
                    'qtd_notas'   => $qtdNotas,
                    'total'       => $totalValor,         // soma das formas de pagamento
                    'faturamento' => $faturamento,        // SUM(TOTAL_NOTA) — fiel à Gestão de Vendas
                    'diferenca'   => $faturamento - $totalValor,
                    'cortesia'    => $cortesia,
                ];
            } catch (\Throwable $e) {
                return ['formas' => [], 'qtd_notas' => 0, 'total' => 0.0, 'cortesia' => 0.0, 'erro' => $e->getMessage()];
            }
        });
    }

    public function anosDisponiveis(): array
    {
        // BETWEEN evita full scan — últimos 5 anos são suficientes
        $anoIni = (int)date('Y') - 4;
        $ini = "{$anoIni}-01-01"; $fim = date('Y') . '-12-31';
        $st = $this->pdo->prepare("SELECT DISTINCT EXTRACT(YEAR FROM VENCIMENTO) ANO
            FROM RECEBER_PAGAR
            WHERE VENCIMENTO BETWEEN ? AND ?
              AND VENCIMENTO IS NOT NULL
            ORDER BY ANO DESC");
        $st->execute([$ini, $fim]);
        return array_column($st->fetchAll(\PDO::FETCH_ASSOC), 'ANO');
    }

    public function ultimaData(): string
    {
        try {
            // Janela recente (120d) — MAX sem limite custa ~45s no Firebird 2.5 sem índice descendente.
            $r = $this->pdo->query("SELECT MAX(DATA_EMISSAO) D FROM NOTAS_CAB WHERE COD_TIPO_OPERACAO IN (115,231,297) AND COD_SITUACAO <> 3 AND DATA_EMISSAO >= DATEADD(DAY,-120,CURRENT_DATE) AND 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'); }
    }

    // ════════════════════════════════════════════════════════════════════════
    //  MIX DE PAGAMENTO COMPLETO — TODAS as formas (sem filtro de operação)
    //  Fiel ao relatório do ERP — útil pra bar onde cortesia/crediário/delivery
    //  estão em operações diferentes da venda padrão.
    // ════════════════════════════════════════════════════════════════════════
    public function mixPagamentoCompleto(string $dataIni, string $dataFim, ?int $codEmpresa = null): array
    {
        $key = "fin_mix_pagto_{$dataIni}_{$dataFim}_{$codEmpresa}";
        return Cache::lembrar($key, 600, function () use ($dataIni, $dataFim, $codEmpresa) {
            try {
                $wEmp = $codEmpresa ? "AND nc.COD_EMPRESA = {$codEmpresa}" : '';
                $sql = "SELECT
                    UPPER(TRIM(fp.DESCRICAO)) FORMA,
                    fn.COD_FORMA_PGTO COD,
                    COUNT(*) QTD,
                    SUM(fn.VALOR) VALOR
                FROM FORMAS_NOTAS fn
                JOIN FORMA_PGTO fp ON fp.COD_FORMA_PGTO = fn.COD_FORMA_PGTO
                JOIN NOTAS_CAB nc ON nc.NR_NOTA = fn.NR_NOTA
                                 AND nc.SERIE = fn.SERIE
                                 AND nc.COD_EMPRESA = fn.COD_EMPRESA
                                 AND nc.COD_TIPO_OPERACAO = fn.COD_TIPO_OPERACAO
                WHERE nc.DATA_EMISSAO BETWEEN ? AND ?
                  AND nc.DATA_CANC IS NULL
                  {$wEmp}
                GROUP BY UPPER(TRIM(fp.DESCRICAO)), fn.COD_FORMA_PGTO
                ORDER BY 4 DESC";
                $st = $this->pdo->prepare($sql);
                $st->execute([$dataIni, $dataFim]);
                $rows = $st->fetchAll(\PDO::FETCH_ASSOC);

                // Agrupa em 8 grupos lógicos (Dinheiro, Cartão Débito, Cartão Crédito, Pix, A Prazo, Cheque, Delivery, Cortesia, Outros)
                $grupos = [];
                foreach ($rows as $r) {
                    $nome = $r['FORMA'];
                    if (str_contains($nome, 'DEBITO') || str_contains($nome, 'DÉBITO')) $g = 'Cartão Débito';
                    elseif (str_contains($nome, 'CREDITO') && !str_contains($nome, 'CREDIARIO')) $g = 'Cartão Crédito';
                    elseif (str_contains($nome, 'PIX')) $g = 'Pix';
                    elseif ($nome === 'DINHEIRO') $g = 'Dinheiro';
                    elseif ($nome === 'CORTESIA') $g = '🎁 Cortesia';
                    elseif (str_contains($nome, 'CREDIARIO') || str_contains($nome, 'FATURAMENTO') || str_contains($nome, 'BOLETO')) $g = 'A Prazo / Fiado';
                    elseif (str_contains($nome, 'CHEQUE')) $g = 'Cheque';
                    elseif (str_contains($nome, 'PEDIAI') || str_contains($nome, 'IFOOD') || str_contains($nome, 'AIQ')) $g = 'Delivery';
                    else $g = 'Outros';

                    if (!isset($grupos[$g])) {
                        $grupos[$g] = ['GRUPO' => $g, 'VALOR' => 0.0, 'QTD' => 0, 'FORMAS' => []];
                    }
                    $grupos[$g]['VALOR']    += (float)$r['VALOR'];
                    $grupos[$g]['QTD']      += (int)$r['QTD'];
                    $grupos[$g]['FORMAS'][]  = ['nome' => $r['FORMA'], 'valor' => (float)$r['VALOR'], 'qtd' => (int)$r['QTD']];
                }

                // Ordena os grupos por valor desc
                usort($grupos, fn($a, $b) => $b['VALOR'] <=> $a['VALOR']);

                return [
                    'detalhe' => $rows,        // lista nua de formas (15-ish)
                    'grupos'  => array_values($grupos),  // agrupado em 8 grupos
                ];
            } catch (\Throwable $e) {
                return ['detalhe' => [], 'grupos' => [], 'erro' => $e->getMessage()];
            }
        });
    }
}
