<?php

namespace App\Repositories;

use App\Core\Cache;

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

    private function ttl(string $dataFim): int
    {
        return $dataFim >= date('Y-m-d') ? 120 : 1800;
    }

    // Faturamento real = soma das formas de pagamento (TOTAL_ITENS é NULL neste banco)
    private const FAT = "(COALESCE(am.TOTAL_AVISTA,0)+COALESCE(am.TOTA_APRAZO,0)+COALESCE(am.TOTAL_CHEQUE,0)+COALESCE(am.TOTAL_CARTAO,0))";
    private const FAT_PLAIN = "(COALESCE(TOTAL_AVISTA,0)+COALESCE(TOTA_APRAZO,0)+COALESCE(TOTAL_CHEQUE,0)+COALESCE(TOTAL_CARTAO,0))";

    public function kpis(string $dataIni, string $dataFim): array
    {
        $key = "mesas_kpis_{$dataIni}_{$dataFim}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim) {
            $fat = self::FAT_PLAIN;
            $sql = "SELECT
                COUNT(*) QTD_TOTAL,
                COUNT(CASE WHEN STATUS = 'F' THEN 1 END) QTD_FECHADAS,
                COUNT(CASE WHEN STATUS = 'C' THEN 1 END) QTD_CANCELADAS,
                SUM(CASE WHEN STATUS = 'F' THEN {$fat} ELSE 0 END) FATURAMENTO,
                SUM(CASE WHEN STATUS = 'F' THEN COALESCE(TX_SERVICO,0) ELSE 0 END) TX_SERVICO_TOTAL,
                AVG(CASE WHEN STATUS = 'F' THEN {$fat} END) TICKET_MEDIO,
                COUNT(DISTINCT COD_MESA) MESAS_UTILIZADAS
            FROM ABERT_MESA
            WHERE DATA_ABERTURA BETWEEN ? AND ?";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $st->fetch(\PDO::FETCH_ASSOC) ?: [];
        });
    }

    public function monitor(): array
    {
        // Consumo em tempo real: soma dos lançamentos em LANC_MESA (mesas ainda abertas)
        $sql = "SELECT
            am.COD_ABERTURA, am.COD_MESA, am.STATUS,
            am.DATA_ABERTURA, am.HORA_ABERTURA,
            am.QTD_PESSOAS, am.TX_SERVICO,
            COALESCE(cons.TOTAL_ITENS, 0) TOTAL_ITENS,
            p.NOME GARCOM
        FROM ABERT_MESA am
        LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
        LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
        LEFT JOIN (
            SELECT lm.COD_ABERTURA,
                   SUM(lm.QTD * lm.PRECO_UNIT) - SUM(COALESCE(lm.DESCONTO_VLR, 0)) AS TOTAL_ITENS
            FROM LANC_MESA lm
            GROUP BY lm.COD_ABERTURA
        ) cons ON cons.COD_ABERTURA = am.COD_ABERTURA
        WHERE am.STATUS = 'A'
        ORDER BY am.DATA_ABERTURA DESC, am.HORA_ABERTURA DESC";
        return $this->pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function itensMesasAbertas(): array
    {
        $sql = "SELECT
            lm.COD_ABERTURA,
            lm.COD_PRODUTO,
            lm.DESCRICAO,
            lm.QTD,
            lm.PRECO_UNIT,
            lm.QTD * lm.PRECO_UNIT - COALESCE(lm.DESCONTO_VLR, 0) SUBTOTAL,
            lm.DATAHORAMOV,
            lm.OBSERVACAO
        FROM LANC_MESA lm
        JOIN ABERT_MESA am ON am.COD_ABERTURA = lm.COD_ABERTURA
        WHERE am.STATUS = 'A'
        ORDER BY lm.COD_ABERTURA, lm.DATAHORAMOV";
        $rows = $this->pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC);

        // Agrupa por COD_ABERTURA
        $agrupado = [];
        foreach ($rows as $r) {
            $agrupado[$r['COD_ABERTURA']][] = $r;
        }
        return $agrupado;
    }

    // ════════════════════════════════════════════════════════════════════════
    //  EXCEÇÕES NO CAIXA — anti-fraude bar (Tumbleweed pattern)
    // ════════════════════════════════════════════════════════════════════════

    /**
     * Resumo por garçom com indicadores de exceção:
     *  - mesas atendidas / faturamento
     *  - canceladas (STATUS='C') + % do total
     *  - desconto total + % do faturamento
     *  - comandas longas (>4h sem fechar)
     *  - score consolidado (0-100, maior = mais suspeito)
     */
    public function excecoesPorGarcom(string $dataIni, string $dataFim, float $thresholdCancPct = 5.0, float $thresholdDescPct = 10.0, int $horasLonga = 4): array
    {
        $key = "mesas_excecoes_gar_{$dataIni}_{$dataFim}_{$thresholdCancPct}_{$thresholdDescPct}_{$horasLonga}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim, $thresholdCancPct, $thresholdDescPct, $horasLonga) {
            $fat = self::FAT;
            // Usuários técnicos que NÃO são garçons reais (caixa, admin, etc.) — falso positivo.
            $excluirNomes = ['CAIXA', 'ADMIN', 'OPERADOR', 'SISTEMA', 'GERENTE', 'ADMINISTRADOR', 'SUPORTE'];

            // Traz dados crus (sem EXTRACT — HORA é VARCHAR em alguns ERPs Fire).
            // Cálculo de "comanda longa" feito em PHP via strtotime, mais portável.
            // DESCONTO somado de LANC_MESA.DESCONTO_VLR porque ABERT_MESA.DESCONTO
            // costuma ficar zerado nesse ERP (o desconto real fica linha-a-linha em LANC_MESA).
            $sql = "SELECT
                am.COD_ABERTURA,
                am.COD_GARCOM,
                COALESCE(p.NOME, '(sem garçom)') GARCOM,
                am.STATUS,
                am.DATA_ABERTURA, am.HORA_ABERTURA,
                am.DATA_FECHAMENTO, am.HORA_FECHAMENTO,
                {$fat} TOTAL,
                COALESCE(am.DESCONTO,0) + COALESCE((
                    SELECT SUM(COALESCE(lm.DESCONTO_VLR, 0))
                    FROM LANC_MESA lm
                    WHERE lm.COD_ABERTURA = am.COD_ABERTURA
                ), 0) DESCONTO
            FROM ABERT_MESA am
            LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
            LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
            WHERE am.DATA_ABERTURA BETWEEN ? AND ?
              AND am.STATUS IN ('F','C')";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            $rows = $st->fetchAll(\PDO::FETCH_ASSOC);

            // Filtra usuários técnicos (CAIXA, ADMIN, etc.)
            $rows = array_filter($rows, function ($r) use ($excluirNomes) {
                $nome = strtoupper(trim($r['GARCOM'] ?? ''));
                foreach ($excluirNomes as $excl) {
                    if (str_starts_with($nome, $excl)) return false;
                }
                return true;
            });

            // Agrega por garçom no PHP
            $agg = [];
            $longaSeg = $horasLonga * 3600;
            foreach ($rows as $r) {
                $cod = (int)($r['COD_GARCOM'] ?? 0);
                if (!isset($agg[$cod])) {
                    $agg[$cod] = [
                        'COD_GARCOM'       => $cod,
                        'GARCOM'           => $r['GARCOM'] ?? '(sem garçom)',
                        'MESAS_TOTAL'      => 0,
                        'MESAS_FECHADAS'   => 0,
                        'MESAS_CANCELADAS' => 0,
                        'FATURAMENTO'      => 0.0,
                        'DESCONTO_TOTAL'   => 0.0,
                        'VALOR_CANCELADO'  => 0.0,
                        'COMANDAS_LONGAS'  => 0,
                    ];
                }
                $a = &$agg[$cod];
                $a['MESAS_TOTAL']++;
                $total = (float)$r['TOTAL'];

                if ($r['STATUS'] === 'F') {
                    $a['MESAS_FECHADAS']++;
                    $a['FATURAMENTO']    += $total;
                    $a['DESCONTO_TOTAL'] += (float)$r['DESCONTO'];

                    // duração da comanda
                    if (!empty($r['DATA_FECHAMENTO']) && !empty($r['HORA_ABERTURA']) && !empty($r['HORA_FECHAMENTO'])) {
                        $ini = strtotime(substr($r['DATA_ABERTURA'],0,10)   . ' ' . substr($r['HORA_ABERTURA'],0,8));
                        $fim = strtotime(substr($r['DATA_FECHAMENTO'],0,10) . ' ' . substr($r['HORA_FECHAMENTO'],0,8));
                        if ($ini && $fim && ($fim - $ini) > $longaSeg) $a['COMANDAS_LONGAS']++;
                    }
                } elseif ($r['STATUS'] === 'C') {
                    $a['MESAS_CANCELADAS']++;
                    $a['VALOR_CANCELADO'] += $total;
                }
                unset($a);
            }

            // Score (0-100): combina taxa de cancelamento, taxa de desconto e contagem de longas.
            $out = [];
            foreach ($agg as $r) {
                $total = max(1, (int)$r['MESAS_TOTAL']);
                $faturRef = max(1, (float)$r['FATURAMENTO']);
                $pctCanc = ((int)$r['MESAS_CANCELADAS']) * 100.0 / $total;
                $pctDesc = ((float)$r['DESCONTO_TOTAL']) * 100.0 / $faturRef;
                $pctLong = ((int)$r['COMANDAS_LONGAS']) * 100.0 / $total;

                $r['PCT_CANCELADAS'] = round($pctCanc, 2);
                $r['PCT_DESCONTO']   = round($pctDesc, 2);
                $r['PCT_LONGAS']     = round($pctLong, 2);

                // Score com curva tanh suave: cada componente vai pra ~95 mas nunca trava em 100
                // mesmo com valores muito altos — assim 9.3% vs 14.8% canc dão SCORE diferente.
                $compCanc = tanh($pctCanc / max(0.1, $thresholdCancPct) / 3.0) * 50;
                $compDesc = tanh($pctDesc / max(0.1, $thresholdDescPct) / 3.0) * 30;
                $compLong = tanh($pctLong / 30.0) * 20;
                $score = $compCanc + $compDesc + $compLong;

                $r['SCORE']    = round($score, 1);
                $r['SEMAFORO'] = $score >= 60 ? 'vermelho' : ($score >= 30 ? 'amarelo' : 'verde');
                $out[] = $r;
            }
            // Ordena por mesas total desc
            usort($out, fn($a, $b) => $b['MESAS_TOTAL'] - $a['MESAS_TOTAL']);
            return $out;
        });
    }

    /**
     * Drill-down: mesas problemáticas de UM garçom.
     * Retorna mesa por mesa com flag (cancelada / desconto-alto / longa).
     */
    public function excecoesDetalhe(int $codGarcom, string $dataIni, string $dataFim, float $thresholdDescPct = 15.0, int $horasLonga = 4): array
    {
        $key = "mesas_excecoes_det_{$codGarcom}_{$dataIni}_{$dataFim}_{$thresholdDescPct}_{$horasLonga}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($codGarcom, $dataIni, $dataFim, $thresholdDescPct, $horasLonga) {
            $fat = self::FAT;
            $sql = "SELECT FIRST 300
                am.COD_ABERTURA, am.COD_MESA, am.STATUS,
                am.DATA_ABERTURA, am.HORA_ABERTURA,
                am.DATA_FECHAMENTO, am.HORA_FECHAMENTO,
                {$fat} TOTAL,
                COALESCE(am.DESCONTO,0) DESCONTO,
                am.QTD_PESSOAS
            FROM ABERT_MESA am
            WHERE am.COD_GARCOM = {$codGarcom}
              AND am.DATA_ABERTURA BETWEEN ? AND ?
              AND am.STATUS IN ('F','C')
            ORDER BY am.DATA_ABERTURA DESC, am.HORA_ABERTURA DESC";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            $rows = $st->fetchAll(\PDO::FETCH_ASSOC);

            // Classifica cada linha
            $out = [];
            foreach ($rows as $r) {
                $total = (float)$r['TOTAL'];
                $desc  = (float)$r['DESCONTO'];
                $pctDesc = $total > 0 ? ($desc * 100.0 / ($total + $desc)) : 0.0;
                $flags = [];

                if ($r['STATUS'] === 'C') $flags[] = 'cancelada';
                if ($pctDesc >= $thresholdDescPct) $flags[] = 'desconto-alto';

                // calcula minutos de comanda
                $minutos = 0;
                if ($r['DATA_FECHAMENTO'] && $r['HORA_ABERTURA'] && $r['HORA_FECHAMENTO']) {
                    $ini = strtotime(substr($r['DATA_ABERTURA'],0,10) . ' ' . substr($r['HORA_ABERTURA'],0,8));
                    $fim = strtotime(substr($r['DATA_FECHAMENTO'],0,10) . ' ' . substr($r['HORA_FECHAMENTO'],0,8));
                    if ($ini && $fim && $fim > $ini) $minutos = (int)(($fim - $ini) / 60);
                }
                if ($minutos > $horasLonga * 60) $flags[] = 'comanda-longa';

                $r['PCT_DESCONTO'] = round($pctDesc, 2);
                $r['MINUTOS']      = $minutos;
                $r['FLAGS']        = $flags;

                // Só retorna mesas COM exceção (filtra "limpas")
                if (!empty($flags)) $out[] = $r;
            }
            return $out;
        });
    }

    // ════════════════════════════════════════════════════════════════════════
    //  COMPARATIVO POR HORA — hoje vs mesma hora 7 dias atrás vs 1 ano atrás
    //  (#2 do roadmap — Toast Now / Square Live Sales mobile)
    // ════════════════════════════════════════════════════════════════════════

    public function comparativoPorHora(string $data): array
    {
        $key = "mesas_cmp_hora_{$data}";
        return Cache::lembrar($key, 180, function () use ($data) {
            $dataSemana = date('Y-m-d', strtotime("$data -7 days"));
            $dataAno    = date('Y-m-d', strtotime("$data -1 year"));

            $sql = "SELECT EXTRACT(HOUR FROM nc.DATA_HORA_VENDA) HORA,
                           COUNT(*) QTD,
                           COALESCE(SUM(nc.TOTAL_NOTA), 0) TOTAL
                    FROM NOTAS_CAB nc
                    WHERE nc.DATA_EMISSAO = ?
                      AND nc.DATA_CANC IS NULL
                    GROUP BY EXTRACT(HOUR FROM nc.DATA_HORA_VENDA)
                    ORDER BY 1";
            $st = $this->pdo->prepare($sql);

            $coletar = function ($d) use ($st) {
                $out = array_fill(0, 24, ['qtd' => 0, 'total' => 0.0]);
                try {
                    $st->execute([$d]);
                    foreach ($st->fetchAll(\PDO::FETCH_ASSOC) as $r) {
                        $h = (int)$r['HORA'];
                        if ($h >= 0 && $h < 24) {
                            $out[$h] = ['qtd' => (int)$r['QTD'], 'total' => (float)$r['TOTAL']];
                        }
                    }
                } catch (\Throwable) {}
                return $out;
            };

            return [
                'hoje'      => $coletar($data),
                'semana'    => $coletar($dataSemana),
                'ano'       => $coletar($dataAno),
                'data'      => $data,
                'dataSemana'=> $dataSemana,
                'dataAno'   => $dataAno,
            ];
        });
    }

    // ════════════════════════════════════════════════════════════════════════
    //  FAIXAS DE HORÁRIO DO BAR — Esquenta / Pico / Madrugada / Saideira
    //  (#4 do roadmap — "daypart" adaptado pra bar noturno brasileiro)
    // ════════════════════════════════════════════════════════════════════════

    /**
     * Faixas (configuráveis):
     *   Esquenta:  [18, 21)   — 18h-21h
     *   Pico:      [21, 24)   — 21h-00h
     *   Madrugada: [0, 3)     — 00h-03h
     *   Saideira:  [3, 6)     — 03h-06h
     *   Dia:       [6, 18)    — 06h-18h (em bar noturno fica zerado, mas listamos)
     */
    public function faixasHorarioBar(string $dataIni, string $dataFim): array
    {
        $key = "mesas_faixas_bar_{$dataIni}_{$dataFim}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim) {
            $sql = "SELECT EXTRACT(HOUR FROM nc.DATA_HORA_VENDA) HORA,
                           COUNT(*) QTD,
                           COALESCE(SUM(nc.TOTAL_NOTA), 0) TOTAL
                    FROM NOTAS_CAB nc
                    WHERE nc.DATA_EMISSAO BETWEEN ? AND ?
                      AND nc.DATA_CANC IS NULL
                    GROUP BY EXTRACT(HOUR FROM nc.DATA_HORA_VENDA)
                    ORDER BY 1";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            $rows = $st->fetchAll(\PDO::FETCH_ASSOC);

            // Faixas em ordem de exibição
            $faixas = [
                ['chave' => 'esquenta',  'nome' => '🍻 Esquenta',  'periodo' => '18h às 21h', 'horas' => [18, 19, 20],         'cor' => '#f59e0b'],
                ['chave' => 'pico',      'nome' => '🔥 Pico',      'periodo' => '21h às 00h', 'horas' => [21, 22, 23],         'cor' => '#dc2626'],
                ['chave' => 'madrugada', 'nome' => '🌙 Madrugada', 'periodo' => '00h às 03h', 'horas' => [0, 1, 2],            'cor' => '#6366f1'],
                ['chave' => 'saideira',  'nome' => '🍺 Saideira',  'periodo' => '03h às 06h', 'horas' => [3, 4, 5],            'cor' => '#8b5cf6'],
                ['chave' => 'dia',       'nome' => '☀️ Dia',       'periodo' => '06h às 18h', 'horas' => range(6, 17),         'cor' => '#16a34a'],
            ];

            // Soma cada faixa
            foreach ($faixas as &$f) {
                $f['qtd']   = 0;
                $f['total'] = 0.0;
                foreach ($rows as $r) {
                    $h = (int)$r['HORA'];
                    if (in_array($h, $f['horas'], true)) {
                        $f['qtd']   += (int)$r['QTD'];
                        $f['total'] += (float)$r['TOTAL'];
                    }
                }
                $f['ticket'] = $f['qtd'] > 0 ? $f['total'] / $f['qtd'] : 0;
            }
            unset($f);
            // Remove faixas zeradas (ex.: Dia em bar noturno) — mas só se TUDO zero
            $totalGeral = 0;
            foreach ($faixas as $f) $totalGeral += $f['total'];

            return [
                'faixas'     => $faixas,
                'totalGeral' => $totalGeral,
                'dataIni'    => $dataIni,
                'dataFim'    => $dataFim,
            ];
        });
    }

    // ════════════════════════════════════════════════════════════════════════
    //  CORTESIA — fraude clássica do bar (forma de pagamento = CORTESIA)
    // ════════════════════════════════════════════════════════════════════════

    /**
     * Notas com forma de pagamento "CORTESIA" no período.
     * Retorna detalhe nota a nota com cliente, garçom e valor.
     */
    public function cortesiasDetalhe(string $dataIni, string $dataFim, int $limite = 500): array
    {
        $key = "mesas_cortesia_det_{$dataIni}_{$dataFim}_{$limite}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim, $limite) {
            try {
                $sql = "SELECT FIRST {$limite}
                    nc.NR_NOTA, nc.SERIE, nc.COD_EMPRESA, nc.COD_TIPO_OPERACAO,
                    nc.DATA_EMISSAO, nc.HORA_EMISSAO,
                    TRIM(COALESCE(pc.NOME, nc.NOME_CONSUMIDOR, '(consumidor)')) CLIENTE,
                    nc.COD_PESSOA_VENDA,
                    TRIM(COALESCE(pv.NOME, '(sem garçom)')) GARCOM,
                    fn.VALOR,
                    nc.TOTAL_NOTA
                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
                LEFT JOIN PESSOAS pc ON pc.COD_PESSOA = nc.COD_PESSOA
                LEFT JOIN PESSOAS pv ON pv.COD_PESSOA = nc.COD_PESSOA_VENDA
                WHERE UPPER(TRIM(fp.DESCRICAO)) = 'CORTESIA'
                  AND nc.DATA_EMISSAO BETWEEN ? AND ?
                  AND nc.DATA_CANC IS NULL
                ORDER BY nc.DATA_EMISSAO DESC, nc.HORA_EMISSAO DESC";
                $st = $this->pdo->prepare($sql);
                $st->execute([$dataIni, $dataFim]);
                return $st->fetchAll(\PDO::FETCH_ASSOC);
            } catch (\Throwable) {
                return [];
            }
        });
    }

    /**
     * Cortesias agregadas por garçom (COD_PESSOA_VENDA).
     * Inclui # cortesias, valor total e # cortesias / total de notas do garçom (intensidade).
     */
    public function cortesiasPorGarcom(string $dataIni, string $dataFim): array
    {
        $key = "mesas_cortesia_gar_{$dataIni}_{$dataFim}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim) {
            try {
                // Agrupa por vendedor (COD_PESSOA_VENDA)
                $sql = "SELECT
                    nc.COD_PESSOA_VENDA,
                    TRIM(COALESCE(pv.NOME, '(sem garçom)')) GARCOM,
                    COUNT(*) QTD_CORTESIAS,
                    SUM(fn.VALOR) VALOR_CORTESIA
                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
                LEFT JOIN PESSOAS pv ON pv.COD_PESSOA = nc.COD_PESSOA_VENDA
                WHERE UPPER(TRIM(fp.DESCRICAO)) = 'CORTESIA'
                  AND nc.DATA_EMISSAO BETWEEN ? AND ?
                  AND nc.DATA_CANC IS NULL
                GROUP BY nc.COD_PESSOA_VENDA, pv.NOME
                ORDER BY 4 DESC";
                $st = $this->pdo->prepare($sql);
                $st->execute([$dataIni, $dataFim]);
                $rows = $st->fetchAll(\PDO::FETCH_ASSOC);

                // Calcula média do bar pra comparativo
                $totalCorte = 0; $totalQtd = 0;
                foreach ($rows as $r) {
                    $totalCorte += (float)$r['VALOR_CORTESIA'];
                    $totalQtd   += (int)$r['QTD_CORTESIAS'];
                }
                $mediaValor = count($rows) > 0 ? $totalCorte / count($rows) : 0;

                foreach ($rows as &$r) {
                    $valor = (float)$r['VALOR_CORTESIA'];
                    $r['VS_MEDIA'] = $mediaValor > 0 ? round(($valor / $mediaValor - 1) * 100, 1) : 0;
                    // Suspeito: garçom com >2x a média do bar
                    $r['ALERTA'] = $mediaValor > 0 && $valor > $mediaValor * 2;
                }
                return $rows;
            } catch (\Throwable) {
                return [];
            }
        });
    }

    /** Lista todos os garçons cadastrados (sem EXISTS pesado em ABERT_MESA — dava timeout no Real Prime). */
    public function listarGarcons(): array
    {
        return Cache::lembrar('mesas_garcons_lista', 1800, function () {
            $sql = "SELECT g.COD_GARCOM, COALESCE(p.NOME, 'Sem nome') NOME
                    FROM GARCOM g
                    LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
                    ORDER BY 2";
            return $this->pdo->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    /** Mesas fechadas (ou canceladas) abertas por um garçom específico no período. */
    public function mesasFechadasDoGarcom(int $codGarcom, string $dataIni, string $dataFim, int $limite = 500): array
    {
        $key = "mesas_fechadas_garcom_{$codGarcom}_{$dataIni}_{$dataFim}_{$limite}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($codGarcom, $dataIni, $dataFim, $limite) {
            $fat = self::FAT;
            $sql = "SELECT FIRST {$limite}
                am.COD_ABERTURA, am.COD_MESA, am.STATUS,
                am.DATA_ABERTURA, am.HORA_ABERTURA,
                am.DATA_FECHAMENTO, am.HORA_FECHAMENTO,
                {$fat} TOTAL_ITENS,
                am.QTD_PESSOAS, COALESCE(am.TX_SERVICO,0) TX_SERVICO,
                COALESCE(am.DESCONTO,0) DESCONTO,
                COALESCE(am.TOTAL_AVISTA,0)  TOTAL_AVISTA,
                COALESCE(am.TOTAL_CARTAO,0)  TOTAL_CARTAO,
                COALESCE(am.TOTAL_CHEQUE,0)  TOTAL_CHEQUE,
                COALESCE(am.TOTA_APRAZO,0)   TOTAL_APRAZO,
                p.NOME GARCOM
            FROM ABERT_MESA am
            LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
            LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
            WHERE am.COD_GARCOM = {$codGarcom}
              AND am.DATA_ABERTURA BETWEEN ? AND ?
              AND am.STATUS IN ('F','C')
            ORDER BY am.DATA_ABERTURA DESC, am.HORA_ABERTURA DESC";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $st->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    /** Itens de UMA mesa específica (todos os lançamentos de LANC_MESA da abertura X). */
    public function itensDaMesa(int $codAbertura): array
    {
        return Cache::lembrar("mesas_itens_{$codAbertura}", 1800, function () use ($codAbertura) {
            $sql = "SELECT
                lm.COD_PRODUTO,
                lm.DESCRICAO,
                lm.QTD,
                lm.PRECO_UNIT,
                lm.QTD * lm.PRECO_UNIT - COALESCE(lm.DESCONTO_VLR, 0) SUBTOTAL,
                COALESCE(lm.DESCONTO_VLR, 0) DESCONTO_VLR,
                lm.DATAHORAMOV,
                lm.OBSERVACAO
            FROM LANC_MESA lm
            WHERE lm.COD_ABERTURA = ?
            ORDER BY lm.DATAHORAMOV";
            $st = $this->pdo->prepare($sql);
            $st->execute([$codAbertura]);
            return $st->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    public function historico(string $dataIni, string $dataFim, int $limite = 200): array
    {
        $key = "mesas_hist_{$dataIni}_{$dataFim}_{$limite}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim, $limite) {
            $fat = self::FAT;
            $sql = "SELECT FIRST {$limite}
                am.COD_ABERTURA, am.COD_MESA, am.STATUS,
                am.DATA_ABERTURA, am.HORA_ABERTURA,
                am.DATA_FECHAMENTO, am.HORA_FECHAMENTO,
                {$fat} TOTAL_ITENS,
                am.QTD_PESSOAS, am.TX_SERVICO, am.DESCONTO,
                p.NOME GARCOM
            FROM ABERT_MESA am
            LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
            LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
            WHERE am.DATA_ABERTURA BETWEEN ? AND ?
              AND am.STATUS IN ('F','C')
            ORDER BY am.DATA_ABERTURA DESC, am.HORA_ABERTURA DESC";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $st->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    public function porGarcom(string $dataIni, string $dataFim): array
    {
        $key = "mesas_garcom_{$dataIni}_{$dataFim}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim) {
            $fat = self::FAT;
            $sql = "SELECT
                COALESCE(p.NOME, 'Sem Garçom') GARCOM,
                COUNT(*) QTD_MESAS,
                SUM(CASE WHEN am.STATUS='F' THEN {$fat} ELSE 0 END) FATURAMENTO,
                AVG(CASE WHEN am.STATUS='F' THEN {$fat} END) TICKET_MEDIO,
                SUM(CASE WHEN am.STATUS='F' THEN COALESCE(am.TX_SERVICO,0) ELSE 0 END) TX_SERVICO,
                SUM(CASE WHEN am.STATUS='F' THEN COALESCE(am.QTD_PESSOAS,0) ELSE 0 END) QTD_PESSOAS
            FROM ABERT_MESA am
            LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
            LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
            WHERE am.DATA_ABERTURA BETWEEN ? AND ?
              AND am.STATUS IN ('F','C')
            GROUP BY p.NOME
            ORDER BY FATURAMENTO DESC";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $st->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    public function tempoMedio(string $dataIni, string $dataFim): array
    {
        return Cache::lembrar("mesas_tempo_medio_{$dataIni}_{$dataFim}", $this->ttl($dataFim), function () use ($dataIni, $dataFim) {
            $fat = self::FAT;
            $sql = "SELECT FIRST 300
                am.COD_MESA,
                am.DATA_ABERTURA,
                am.HORA_ABERTURA,
                am.HORA_FECHAMENTO,
                {$fat} TOTAL_ITENS,
                am.QTD_PESSOAS,
                p.NOME GARCOM
            FROM ABERT_MESA am
            LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
            LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
            WHERE am.DATA_ABERTURA BETWEEN ? AND ?
              AND am.STATUS = 'F'
              AND am.HORA_ABERTURA IS NOT NULL
              AND am.HORA_FECHAMENTO IS NOT NULL
            ORDER BY am.DATA_ABERTURA DESC";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $st->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    public function faturamentoPorDia(string $dataIni, string $dataFim): array
    {
        $key = "mesas_fat_dia_{$dataIni}_{$dataFim}";
        return Cache::lembrar($key, $this->ttl($dataFim), function () use ($dataIni, $dataFim) {
            $fat = self::FAT_PLAIN;
            $sql = "SELECT
                DATA_ABERTURA DATA,
                COUNT(*) QTD_MESAS,
                SUM({$fat}) FATURAMENTO,
                AVG({$fat}) TICKET_MEDIO
            FROM ABERT_MESA
            WHERE DATA_ABERTURA BETWEEN ? AND ? AND STATUS = 'F'
            GROUP BY DATA_ABERTURA
            ORDER BY DATA_ABERTURA";
            $st = $this->pdo->prepare($sql);
            $st->execute([$dataIni, $dataFim]);
            return $st->fetchAll(\PDO::FETCH_ASSOC);
        });
    }

    public function comissoesPorGarcom(string $dataIni, string $dataFim, float $perc = 10.0): array
    {
        $fat = self::FAT;
        $sql = "SELECT
            COALESCE(p.NOME, 'Sem Garçom') GARCOM,
            COUNT(*) QTD_MESAS,
            SUM(CASE WHEN am.STATUS='F' THEN {$fat} ELSE 0 END) FATURAMENTO,
            SUM(CASE WHEN am.STATUS='F' THEN COALESCE(am.TX_SERVICO,0) ELSE 0 END) TX_SERVICO,
            SUM(CASE WHEN am.STATUS='F' THEN {$fat} ELSE 0 END) * ? / 100.0 COMISSAO
        FROM ABERT_MESA am
        LEFT JOIN GARCOM g ON am.COD_GARCOM = g.COD_GARCOM
        LEFT JOIN PESSOAS p ON g.COD_PESSOA = p.COD_PESSOA
        WHERE am.DATA_ABERTURA BETWEEN ? AND ?
          AND am.STATUS IN ('F','C')
        GROUP BY p.NOME
        ORDER BY FATURAMENTO DESC";
        $st = $this->pdo->prepare($sql);
        $st->execute([$perc, $dataIni, $dataFim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function comissoesMensal(string $dataIni, string $dataFim, float $perc = 10.0): array
    {
        $fat = self::FAT_PLAIN;
        $sql = "SELECT
            EXTRACT(YEAR FROM DATA_ABERTURA) ANO,
            EXTRACT(MONTH FROM DATA_ABERTURA) MES,
            SUM({$fat}) FATURAMENTO,
            SUM({$fat}) * ? / 100.0 COMISSAO
        FROM ABERT_MESA
        WHERE DATA_ABERTURA BETWEEN ? AND ? AND STATUS = 'F'
        GROUP BY EXTRACT(YEAR FROM DATA_ABERTURA), EXTRACT(MONTH FROM DATA_ABERTURA)
        ORDER BY ANO, MES";
        $st = $this->pdo->prepare($sql);
        $st->execute([$perc, $dataIni, $dataFim]);
        return $st->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function ultimaData(): string
    {
        try {
            $r = $this->pdo->query("SELECT MAX(DATA_ABERTURA) D FROM ABERT_MESA WHERE STATUS = 'F'")->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'); }
    }
}
