import { ForbiddenException, Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import type {
  RepDashboard,
  SupervisorDashboard,
  ManagerDashboard,
  RankingRep,
  PositivacaoRep,
} from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types';
import { getTeamCodes } from '../workspace/team-scope.util';

// Situa ERP: 2=Liberado, 4=Faturado, 5=Cancelado
// Situa SAR (pedidos novos): 1=Pendente, 2=Aprovado, 3=Cancelado, 4=Faturado
const SITUA_PENDENTE = 1;

// vw_metas.tipo (gestao.metavenda): GL = meta global, GR = meta por grupo.
const TIPO_META_GLOBAL = 'GL';
const TIPO_META_GRUPO = 'GR';

// Metas/produtos vivem na MATRIZ; pedidos usam idEmpresa cru (ex.: 9001 → matriz 1).
function matrizEmpresa(idEmpresa: number): number {
  return idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;
}

interface MetaErpRow {
  tipo: string;
  cod_grupo: number | null;
  desc_grupo: string | null;
  valor: string;
  qtdade: string;
  peso: string;
  vl_fator: string;
}

interface RealizadoGrupoRow {
  cod_grupo: number | null;
  grupo: string | null;
  pedidos: string;
  valor: string;
  qtd: string;
  peso: string;
}

interface NaoPositivadoRow {
  id_cliente: number;
  nome: string;
  razao: string | null;
  dt_ultimo_pedido: string | null; // YYYY-MM-DD ou null
  dias_sem_pedido: number | null;
  comprou_antes: boolean;
  whatsapp: string | null;
}

interface InativosPorRepRow {
  cod_vendedor: number;
  nome_vendedor: string | null;
  inativos_count: string;
}

@Injectable()
export class DashboardService {
  constructor(private readonly cls: ClsService<WorkspaceClsStore>) {}

  async repDashboard(userId: string): Promise<RepDashboard> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const codVendedor = parseInt(userId, 10);

    const now = new Date();
    const year = now.getFullYear();
    const month = now.getMonth() + 1;

    const monthStart = new Date(year, month - 1, 1);
    const monthEnd = new Date(year, month, 0, 23, 59, 59, 999);

    // 1. Metas do mês — vw_metas vive na matriz (normaliza 9001→1).
    //    GL = meta global; GR = meta por grupo. A dimensão segue o que o ERP tiver.
    const idEmpresaMatriz = matrizEmpresa(idEmpresa);
    const metaErpRows = await prisma.$queryRawUnsafe<MetaErpRow[]>(`
      SELECT TRIM(tipo) AS tipo, cod_grupo, TRIM(desc_grupo) AS desc_grupo,
             valor::text AS valor, qtdade::text AS qtdade,
             peso::text AS peso, vl_fator::text AS vl_fator
      FROM vw_metas
      WHERE id_empresa   = ${idEmpresaMatriz}
        AND cod_vendedor = ${codVendedor}
        AND ano          = ${year}
        AND mes          = ${month}
        AND TRIM(tipo) IN ('${TIPO_META_GLOBAL}', '${TIPO_META_GRUPO}')
    `);
    const glRows = metaErpRows.filter((m) => m.tipo === TIPO_META_GLOBAL);
    const grRows = metaErpRows.filter((m) => m.tipo === TIPO_META_GRUPO);
    // Total global: usa GL se houver; senão soma as metas por grupo (GR).
    const targetAmount = glRows.length
      ? glRows.reduce((a, m) => a + Number(m.valor), 0)
      : grRows.reduce((a, m) => a + Number(m.valor), 0);
    const metaDimensao = grRows.length > 0 ? ('grupo' as const) : ('global' as const);

    // 2. Atingido do mês — realizado = tudo menos Cancelado(5) e Pendente/não-transmitido(1).
    //    Inclui Liberado(2), Enviado(3,6,92,95,200) e Faturado(4). Base: data do pedido.
    const monthStartStr = monthStart.toISOString().slice(0, 10);
    const monthEndStr = monthEnd.toISOString().slice(0, 10);

    interface TotalRow {
      total: string;
    }
    interface CountRow {
      count: string;
    }
    interface RecentRow {
      id_pedido: number;
      num_ped_sar: string;
      numero: number;
      id_cliente: number;
      nome_cliente: string | null;
      razao_cliente: string | null;
      cod_vendedor: number;
      nome_vendedor: string | null;
      situa: number;
      status_descr: string;
      dt_pedido: Date;
      total: string;
      desconto_perc: string;
      obs: string | null;
    }

    interface RealizadoMesRow {
      mes: string;
      valor: string;
    }
    interface MetaMesRow {
      mes: string;
      tipo: string;
      valor: string;
    }

    const [
      atingidoRows,
      pedidosMesRows,
      recentRows,
      realizadoGrupoRows,
      realizadoMesRows,
      metaMesRows,
    ] = await Promise.all([
      prisma.$queryRawUnsafe<TotalRow[]>(`
        SELECT COALESCE(SUM(total), 0)::text AS total
        FROM vw_pedidos_erp
        WHERE id_empresa   = ${idEmpresa}
          AND cod_vendedor = ${codVendedor}
          AND situa        NOT IN (1, 5)
          AND dt_pedido   >= '${monthStartStr}'
          AND dt_pedido   <= '${monthEndStr}'
      `),
      prisma.$queryRawUnsafe<CountRow[]>(`
        SELECT COUNT(*)::text AS count
        FROM vw_pedidos_erp
        WHERE id_empresa   = ${idEmpresa}
          AND cod_vendedor = ${codVendedor}
          AND situa        != 5
          AND dt_pedido   >= '${monthStartStr}'
          AND dt_pedido   <= '${monthEndStr}'
      `),
      prisma.$queryRawUnsafe<RecentRow[]>(`
        SELECT e.id_pedido, e.num_ped_sar, e.numero, e.id_cliente, e.cod_vendedor,
               e.situa, e.status_descr, e.dt_pedido, e.total::text, e.desconto_perc::text, e.obs,
               c.nome AS nome_cliente, c.razao AS razao_cliente,
               (SELECT r.nome FROM vw_representantes r
                  WHERE r.codigo = e.cod_vendedor
                  LIMIT 1) AS nome_vendedor
        FROM vw_pedidos_erp e
        LEFT JOIN vw_clientes c ON c.id_cliente = e.id_cliente
        WHERE e.id_empresa   = ${idEmpresa}
          AND e.cod_vendedor = ${codVendedor}
          AND e.situa        != 5
          AND e.dt_pedido   >= '${new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)}'
        ORDER BY e.dt_pedido DESC
        LIMIT 10
      `),
      // Realizado por grupo — itens faturados (situa 2/4) → produto (matriz) → cod_grupo.
      // peso = qtd × peso_líquido do produto; pedidos = nº de pedidos distintos.
      prisma.$queryRawUnsafe<RealizadoGrupoRow[]>(`
        SELECT p.cod_grupo,
               COALESCE(NULLIF(TRIM(p.grupo), ''), p.cod_grupo::text) AS grupo,
               COUNT(DISTINCT pi.id_pedido)::text AS pedidos,
               COALESCE(SUM(pi.total), 0)::text AS valor,
               COALESCE(SUM(pi.qtd), 0)::text AS qtd,
               COALESCE(SUM(pi.qtd * COALESCE(p.peso_liquido, 0)), 0)::text AS peso
        FROM vw_peditens_erp pi
        JOIN vw_pedidos_erp e ON e.id_pedido = pi.id_pedido
        JOIN vw_produtos p ON p.id_erp = pi.id_produto AND p.id_empresa = ${idEmpresaMatriz}
        WHERE e.id_empresa   = ${idEmpresa}
          AND e.cod_vendedor = ${codVendedor}
          AND e.situa        NOT IN (1, 5)
          AND e.dt_pedido   >= '${monthStartStr}'
          AND e.dt_pedido   <= '${monthEndStr}'
        GROUP BY p.cod_grupo, grupo
      `),
      // Realizado por mês do ano corrente.
      prisma.$queryRawUnsafe<RealizadoMesRow[]>(`
        SELECT EXTRACT(MONTH FROM dt_pedido)::int::text AS mes,
               COALESCE(SUM(total), 0)::text            AS valor
        FROM vw_pedidos_erp
        WHERE id_empresa   = ${idEmpresa}
          AND cod_vendedor = ${codVendedor}
          AND situa        NOT IN (1, 5)
          AND EXTRACT(YEAR FROM dt_pedido) = ${year}
        GROUP BY mes
        ORDER BY mes
      `),
      // Meta (GL + GR) por mês do ano corrente.
      prisma.$queryRawUnsafe<MetaMesRow[]>(`
        SELECT mes::text, TRIM(tipo) AS tipo,
               COALESCE(SUM(valor), 0)::text AS valor
        FROM vw_metas
        WHERE id_empresa   = ${idEmpresaMatriz}
          AND cod_vendedor = ${codVendedor}
          AND ano          = ${year}
          AND TRIM(tipo) IN ('GL', 'GR')
        GROUP BY mes, tipo
        ORDER BY mes
      `),
    ]);

    const atingido = Number(atingidoRows[0]?.total ?? 0);
    const pedidosMes = Number(pedidosMesRows[0]?.count ?? 0);
    const pct = targetAmount > 0 ? Math.round((atingido / targetAmount) * 100) : 0;
    const falta = Math.max(0, targetAmount - atingido);

    // Clientes não positivados no mês: ativos do representante sem pedido desde monthStart.
    // Junta o último pedido histórico (ERP+SAR) e o primeiro whatsapp ativo dos contatos.
    const naoPositivadosRows = await prisma.$queryRawUnsafe<NaoPositivadoRow[]>(`
      WITH ultimo_pedido AS (
        SELECT id_cliente,
               MAX(dt_pedido) AS dt_max
        FROM vw_pedidos_erp
        WHERE situa NOT IN (5)
          AND id_empresa = ${idEmpresa}
        GROUP BY id_cliente
      ),
      pedido_mes AS (
        SELECT DISTINCT id_cliente
        FROM vw_pedidos_erp
        WHERE situa NOT IN (5)
          AND id_empresa = ${idEmpresa}
          AND dt_pedido >= '${monthStartStr}'
      )
      SELECT
        c.id_cliente,
        TRIM(c.nome)                                       AS nome,
        TRIM(c.razao)                                      AS razao,
        TO_CHAR(up.dt_max, 'YYYY-MM-DD')                  AS dt_ultimo_pedido,
        (CURRENT_DATE - up.dt_max::date)                   AS dias_sem_pedido,
        (up.dt_max IS NOT NULL)                            AS comprou_antes,
        ct.whatsapp
      FROM vw_clientes c
      LEFT JOIN ultimo_pedido up ON up.id_cliente = c.id_cliente
      LEFT JOIN pedido_mes   pm ON pm.id_cliente = c.id_cliente
      LEFT JOIN LATERAL (
        SELECT TRIM(whatsapp) AS whatsapp
        FROM sar.vw_contatos
        WHERE id_corrent = c.id_cliente
          AND whatsapp IS NOT NULL AND TRIM(whatsapp) != ''
          AND ativo = 1
        LIMIT 1
      ) ct ON true
      WHERE c.cod_vendedor = ${codVendedor}
        AND c.ativo        = 1
        AND pm.id_cliente  IS NULL
      ORDER BY up.dt_max ASC NULLS FIRST
    `);

    // Metas por grupo: junta meta (GR) com realizado (itens), por cod_grupo.
    const realPorGrupo = new Map<number, RealizadoGrupoRow>();
    for (const r of realizadoGrupoRows) {
      if (r.cod_grupo != null) realPorGrupo.set(Number(r.cod_grupo), r);
    }
    const metasPorGrupo = grRows
      .map((m) => {
        const cod = m.cod_grupo != null ? Number(m.cod_grupo) : null;
        const real = cod != null ? realPorGrupo.get(cod) : undefined;
        const valorMeta = Number(m.valor);
        const valorReal = real ? Number(real.valor) : 0;
        const pesoMeta = Number(m.peso);
        const pesoReal = real ? Number(real.peso) : 0;
        return {
          codigo: cod,
          rotulo: m.desc_grupo || real?.grupo || `Grupo ${cod ?? '?'}`,
          pedidos: real ? Number(real.pedidos) : 0,
          valorMeta,
          valorReal,
          qtdMeta: Number(m.qtdade),
          qtdReal: real ? Number(real.qtd) : 0,
          pesoMeta,
          pesoReal,
          fatorMeta: Number(m.vl_fator),
          fatorReal: pesoReal > 0 ? valorReal / pesoReal : 0,
          pct: valorMeta > 0 ? Math.round((valorReal / valorMeta) * 100) : 0,
          falta: Math.max(0, valorMeta - valorReal),
        };
      })
      .sort((a, b) => b.valorMeta - a.valorMeta);

    // Deduplica por idCliente — vw_clientes pode retornar o mesmo cliente em
    // mais de uma empresa; mantemos a primeira ocorrência (ORDER BY dt_max ASC NULLS FIRST).
    const seenNP = new Set<number>();
    const naoPositivados = naoPositivadosRows
      .filter((c) => {
        const id = Number(c.id_cliente);
        if (seenNP.has(id)) return false;
        seenNP.add(id);
        return true;
      })
      .map((c) => ({
        idCliente: Number(c.id_cliente),
        nome: c.nome,
        razao: c.razao ?? null,
        diasSemPedido: c.dias_sem_pedido != null ? Number(c.dias_sem_pedido) : 999,
        ultimoPedido: c.dt_ultimo_pedido ?? null,
        comprouAntes: Boolean(c.comprou_antes),
        whatsapp: c.whatsapp ?? null,
      }));

    // Monta histório mensal: 12 meses do ano, cruzando realizado e meta.
    const realizadoPorMes = new Map(realizadoMesRows.map((r) => [Number(r.mes), Number(r.valor)]));
    const metaGlPorMes = new Map<number, number>();
    const metaGrPorMes = new Map<number, number>();
    for (const r of metaMesRows) {
      const m = Number(r.mes);
      if (r.tipo === 'GL') metaGlPorMes.set(m, (metaGlPorMes.get(m) ?? 0) + Number(r.valor));
      else metaGrPorMes.set(m, (metaGrPorMes.get(m) ?? 0) + Number(r.valor));
    }
    const historicoMensal = Array.from({ length: 12 }, (_, i) => {
      const m = i + 1;
      const valor = realizadoPorMes.get(m) ?? 0;
      const meta = metaGlPorMes.has(m) ? (metaGlPorMes.get(m) ?? 0) : (metaGrPorMes.get(m) ?? 0);
      return { mes: m, valor, meta, atingiu: meta > 0 && valor >= meta };
    });

    return {
      meta: { atingido, total: targetAmount, pct, falta },
      metaDimensao,
      metasPorGrupo,
      pedidosMes,
      historicoMensal,
      pedidosRecentes: recentRows.map((o) => ({
        id: `erp-${o.id_pedido}`,
        numPedSar: (o.num_ped_sar ?? '').trim(),
        numero: Number(o.numero),
        idCliente: Number(o.id_cliente),
        nomeCliente: o.nome_cliente ?? null,
        razaoCliente: o.razao_cliente ?? null,
        codVendedor: Number(o.cod_vendedor),
        nomeVendedor: o.nome_vendedor ?? null,
        situa: Number(o.situa),
        statusDescr: o.status_descr,
        dtPedido: new Date(o.dt_pedido).toISOString(),
        total: o.total ?? '0',
        descontoPerc: o.desconto_perc ?? '0',
        obs: o.obs ?? null,
        createdAt: new Date(o.dt_pedido).toISOString(),
        fonte: 'erp' as const,
      })),
      naoPositivados,
      totalNaoPositivados: naoPositivados.length,
      syncedAt: now.toISOString(),
    };
  }

  async managerDashboard(mes?: number, ano?: number): Promise<ManagerDashboard> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const now = new Date();

    function matrizEmpresa(id: number): number {
      return id > 9000 ? id - 9000 : id;
    }
    const idEmpresaMatriz = matrizEmpresa(idEmpresa);
    const year = ano ?? now.getFullYear();
    const month = mes ?? now.getMonth() + 1;
    const monthStartStr = new Date(year, month - 1, 1).toISOString().slice(0, 10);
    const monthEndStr = new Date(year, month, 0).toISOString().slice(0, 10);

    interface TotalRow {
      count: string;
      total: string;
    }
    interface RankingRow {
      cod_vendedor: number;
      nome: string | null;
      pedidos: string;
      clientes_atendidos: string;
      faturamento: string;
      meta_valor: string | null;
    }
    interface PromoRow {
      count: string;
    }
    interface MetaTotalRow {
      meta_total: string;
    }
    interface MetaGrupoRow {
      cod_grupo: number | null;
      desc_grupo: string | null;
      valor: string;
      qtdade: string;
      peso: string;
      vl_fator: string;
    }
    interface PositivacaoRow {
      cod_vendedor: number;
      nome_vendedor: string | null;
      total_clientes: string;
      clientes_positivados: string;
    }
    interface PositivacaoDiaRow {
      dia: string;
      clientes: string;
    }
    interface NovosClientesRow {
      cod_vendedor: number;
      novos: string;
    }

    const [
      statsRows,
      rankingRows,
      promoRows,
      metaTotalRows,
      positivacaoRows,
      metaGrupoRows,
      realizadoGrupoRows,
      positivacaoDiariaRows,
      novosClientesRows,
    ] = await Promise.all([
      prisma.$queryRawUnsafe<TotalRow[]>(`
        SELECT COUNT(*)::text AS count, COALESCE(SUM(total), 0)::text AS total
        FROM vw_pedidos_erp
        WHERE id_empresa = ${idEmpresa}
          AND situa      NOT IN (1, 5)
          AND dt_pedido >= '${monthStartStr}'
          AND dt_pedido <= '${monthEndStr}'
      `),
      prisma.$queryRawUnsafe<RankingRow[]>(`
        SELECT p.cod_vendedor,
               (SELECT r.nome FROM vw_representantes r
                  WHERE r.codigo = p.cod_vendedor LIMIT 1)       AS nome,
               COUNT(*)::text                                    AS pedidos,
               COUNT(DISTINCT p.id_cliente)::text               AS clientes_atendidos,
               COALESCE(SUM(p.total), 0)::text                  AS faturamento,
               (SELECT SUM(m.valor)::text FROM sar.vw_metas m
                  WHERE m.id_empresa   = ${idEmpresaMatriz}
                    AND m.cod_vendedor = p.cod_vendedor
                    AND m.ano          = ${year}
                    AND m.mes          = ${month}
                    AND TRIM(m.tipo)   = 'GR')                   AS meta_valor
        FROM vw_pedidos_erp p
        WHERE p.id_empresa = ${idEmpresa}
          AND p.situa      NOT IN (1, 5)
          AND p.dt_pedido >= '${monthStartStr}'
          AND p.dt_pedido <= '${monthEndStr}'
        GROUP BY p.cod_vendedor
        ORDER BY SUM(p.total) DESC
        LIMIT 10
      `),
      prisma.$queryRawUnsafe<PromoRow[]>(`
        SELECT COUNT(*)::text AS count
        FROM sar.promocoes
        WHERE id_empresa = ${idEmpresa}
          AND ativa      = true
          AND data_fim  >= CURRENT_DATE
      `),
      prisma.$queryRawUnsafe<MetaTotalRow[]>(`
        SELECT COALESCE(SUM(valor), 0)::text AS meta_total
        FROM sar.vw_metas
        WHERE id_empresa = ${idEmpresaMatriz}
          AND ano        = ${year}
          AND mes        = ${month}
          AND TRIM(tipo) = 'GR'
      `),
      prisma.$queryRawUnsafe<PositivacaoRow[]>(`
        SELECT
          c.cod_vendedor,
          (SELECT r.nome FROM vw_representantes r WHERE r.codigo = c.cod_vendedor LIMIT 1) AS nome_vendedor,
          COUNT(DISTINCT c.id_cliente)::text                                                AS total_clientes,
          COUNT(DISTINCT CASE WHEN ped.id_cliente IS NOT NULL THEN c.id_cliente END)::text AS clientes_positivados
        FROM vw_clientes c
        LEFT JOIN (
          SELECT DISTINCT id_cliente, cod_vendedor
          FROM vw_pedidos_erp
          WHERE id_empresa = ${idEmpresa}
            AND situa NOT IN (1, 5)
            AND dt_pedido >= '${monthStartStr}'
            AND dt_pedido <= '${monthEndStr}'
        ) ped ON ped.id_cliente = c.id_cliente AND ped.cod_vendedor = c.cod_vendedor
        WHERE c.cod_vendedor > 0
        GROUP BY c.cod_vendedor
        HAVING COUNT(DISTINCT c.id_cliente) > 0
        ORDER BY COUNT(DISTINCT CASE WHEN ped.id_cliente IS NOT NULL THEN c.id_cliente END) DESC
      `),
      prisma.$queryRawUnsafe<MetaGrupoRow[]>(`
        SELECT cod_grupo, TRIM(desc_grupo) AS desc_grupo,
               SUM(valor)::text   AS valor,
               SUM(qtdade)::text  AS qtdade,
               SUM(peso)::text    AS peso,
               AVG(vl_fator)::text AS vl_fator
        FROM sar.vw_metas
        WHERE id_empresa = ${idEmpresaMatriz}
          AND ano        = ${year}
          AND mes        = ${month}
          AND TRIM(tipo) = 'GR'
        GROUP BY cod_grupo, desc_grupo
        ORDER BY SUM(valor) DESC
      `),
      prisma.$queryRawUnsafe<RealizadoGrupoRow[]>(`
        SELECT p.cod_grupo,
               COALESCE(NULLIF(TRIM(p.grupo), ''), p.cod_grupo::text) AS grupo,
               COUNT(DISTINCT pi.id_pedido)::text AS pedidos,
               COALESCE(SUM(pi.total), 0)::text   AS valor,
               COALESCE(SUM(pi.qtd), 0)::text     AS qtd,
               COALESCE(SUM(pi.qtd * COALESCE(p.peso_liquido, 0)), 0)::text AS peso
        FROM vw_peditens_erp pi
        JOIN vw_pedidos_erp e ON e.id_pedido = pi.id_pedido
        JOIN vw_produtos p ON p.id_erp = pi.id_produto AND p.id_empresa = ${idEmpresaMatriz}
        WHERE e.id_empresa = ${idEmpresa}
          AND e.situa      NOT IN (1, 5)
          AND e.dt_pedido >= '${monthStartStr}'
          AND e.dt_pedido <= '${monthEndStr}'
        GROUP BY p.cod_grupo, grupo
      `),
      prisma.$queryRawUnsafe<PositivacaoDiaRow[]>(`
        SELECT dt_pedido::date::text            AS dia,
               COUNT(DISTINCT id_cliente)::text AS clientes
        FROM vw_pedidos_erp
        WHERE id_empresa = ${idEmpresa}
          AND situa      NOT IN (1, 5)
          AND dt_pedido >= '${monthStartStr}'
          AND dt_pedido <= '${monthEndStr}'
        GROUP BY dt_pedido::date
        ORDER BY dia
      `),
      prisma.$queryRawUnsafe<NovosClientesRow[]>(`
        SELECT c.cod_vendedor, COUNT(*)::text AS novos
        FROM (
          SELECT id_cliente, MIN(dt_pedido) AS primeira_compra
          FROM vw_pedidos_erp
          WHERE id_empresa = ${idEmpresa}
            AND situa      NOT IN (1, 5)
          GROUP BY id_cliente
        ) f
        JOIN (SELECT DISTINCT id_cliente, cod_vendedor FROM vw_clientes) c
          ON c.id_cliente = f.id_cliente
        WHERE f.primeira_compra >= '${monthStartStr}'
          AND f.primeira_compra <= '${monthEndStr}'
        GROUP BY c.cod_vendedor
      `),
    ]);

    const pedidosMes = Number(statsRows[0]?.count ?? 0);
    const faturamentoMes = Number(statsRows[0]?.total ?? 0);
    const ticketMedio = pedidosMes > 0 ? faturamentoMes / pedidosMes : 0;

    const realGrupoPorCod = new Map<number, RealizadoGrupoRow>();
    for (const r of realizadoGrupoRows) {
      if (r.cod_grupo != null) realGrupoPorCod.set(Number(r.cod_grupo), r);
    }
    const metasPorGrupo = metaGrupoRows.map((m) => {
      const cod = m.cod_grupo != null ? Number(m.cod_grupo) : null;
      const real = cod != null ? realGrupoPorCod.get(cod) : undefined;
      const valorMeta = Number(m.valor);
      const valorReal = real ? Number(real.valor) : 0;
      const pesoMeta = Number(m.peso);
      const pesoReal = real ? Number(real.peso) : 0;
      return {
        codigo: cod,
        rotulo: m.desc_grupo || real?.grupo || `Grupo ${cod ?? '?'}`,
        pedidos: real ? Number(real.pedidos) : 0,
        valorMeta,
        valorReal,
        qtdMeta: Number(m.qtdade),
        qtdReal: real ? Number(real.qtd) : 0,
        pesoMeta,
        pesoReal,
        fatorMeta: Number(m.vl_fator),
        fatorReal: pesoReal > 0 ? valorReal / pesoReal : 0,
        pct: valorMeta > 0 ? Math.round((valorReal / valorMeta) * 100) : 0,
        falta: Math.max(0, valorMeta - valorReal),
      };
    });

    const rankingReps: RankingRep[] = rankingRows.map((r) => {
      const fat = Number(r.faturamento);
      const meta = r.meta_valor ? Number(r.meta_valor) : 0;
      return {
        codVendedor: Number(r.cod_vendedor),
        nomeVendedor: r.nome ?? null,
        pedidos: Number(r.pedidos),
        clientesAtendidos: Number(r.clientes_atendidos),
        faturamento: fat,
        pctMeta: meta > 0 ? Math.round((fat / meta) * 100) : 0,
      };
    });

    const totalReps = new Set(rankingRows.map((r) => Number(r.cod_vendedor))).size;
    const metaTotal = Number(metaTotalRows[0]?.meta_total ?? 0);

    // Meta diária de positivação salva pelo gerente (config da empresa matriz)
    const config = await prisma.configEmpresa.findUnique({
      where: { idEmpresa: idEmpresaMatriz },
    });

    const novosMap = new Map(
      novosClientesRows.map((n) => [Number(n.cod_vendedor), Number(n.novos)]),
    );
    const positivacaoReps: PositivacaoRep[] = positivacaoRows.map((r) => {
      const total = Number(r.total_clientes);
      const positivados = Number(r.clientes_positivados);
      return {
        codVendedor: Number(r.cod_vendedor),
        nomeVendedor: r.nome_vendedor ?? null,
        totalClientes: total,
        clientesPositivados: positivados,
        pctPositivacao: total > 0 ? Math.round((positivados / total) * 100) : 0,
        novosClientes: novosMap.get(Number(r.cod_vendedor)) ?? 0,
      };
    });

    return {
      faturamentoMes,
      pedidosMes,
      ticketMedio,
      totalReps,
      promocoesAtivas: Number(promoRows[0]?.count ?? 0),
      metaTotal,
      metasPorGrupo,
      rankingReps,
      positivacaoReps,
      positivacaoDiaria: positivacaoDiariaRows.map((r) => ({
        dia: r.dia,
        clientes: Number(r.clientes),
      })),
      metaPositivacaoDia: config?.metaPositivacaoDia ?? null,
      syncedAt: now.toISOString(),
    };
  }

  // Salva a meta diária de positivação (config_empresa) — só gerente/admin.
  async saveMetaPositivacaoDia(meta: number): Promise<void> {
    const role = this.cls.get('role') ?? 'rep';
    if (role !== 'manager' && role !== 'admin') {
      throw new ForbiddenException('Apenas gerentes podem salvar a meta de positivação');
    }
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = matrizEmpresa(this.cls.get('idEmpresa'));

    const value = meta > 0 ? meta : null;
    await prisma.configEmpresa.upsert({
      where: { idEmpresa },
      create: { idEmpresa, metaPositivacaoDia: value },
      update: { metaPositivacaoDia: value },
    });
  }

  async supervisorDashboard(): Promise<SupervisorDashboard> {
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');
    const now = new Date();

    // Supervisor vê só a própria equipe (cod_supervisor em vw_representantes);
    // gerente/admin acessando este painel veem a empresa toda.
    const role = this.cls.get('role');
    const userId = this.cls.get('userId');
    const team =
      role === 'supervisor' ? await getTeamCodes(prisma, userId ? parseInt(userId, 10) : 0) : null;
    const teamSqlFilter = (col: string) => (team ? `AND ${col} IN (${team.join(',')})` : '');

    // Fila de aprovações — pedidos SAR pendentes (novos, ainda não integrados ao ERP)
    const approvalQueue = await prisma.pedido.findMany({
      where: {
        idEmpresa,
        situa: SITUA_PENDENTE,
        ...(team ? { codVendedor: { in: team } } : {}),
      },
      orderBy: { dtPedido: 'asc' },
      take: 50,
    });

    // Pedidos do dia — lê do ERP (situa != 5=Cancelado)
    const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const todayStr = todayStart.toISOString().slice(0, 10);
    const lastWeekStr = new Date(todayStart.getTime() - 7 * 24 * 60 * 60 * 1000)
      .toISOString()
      .slice(0, 10);
    const lastWeekEndStr = new Date(todayStart.getTime() - 6 * 24 * 60 * 60 * 1000)
      .toISOString()
      .slice(0, 10);

    interface DayRow {
      count: string;
      total: string;
    }
    const [todayRows, lastWeekRows] = await Promise.all([
      prisma.$queryRawUnsafe<DayRow[]>(`
        SELECT COUNT(*)::text AS count, COALESCE(SUM(total),0)::text AS total
        FROM vw_pedidos_erp
        WHERE id_empresa = ${idEmpresa} AND situa != 5 AND dt_pedido >= '${todayStr}'
          ${teamSqlFilter('cod_vendedor')}
      `),
      prisma.$queryRawUnsafe<DayRow[]>(`
        SELECT COUNT(*)::text AS count, COALESCE(SUM(total),0)::text AS total
        FROM vw_pedidos_erp
        WHERE id_empresa = ${idEmpresa} AND situa != 5
          AND dt_pedido >= '${lastWeekStr}' AND dt_pedido < '${lastWeekEndStr}'
          ${teamSqlFilter('cod_vendedor')}
      `),
    ]);

    // Top 3 reps com mais clientes inativos (>30 dias sem compra no ERP)
    const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
    const inativosPorRep = await prisma.$queryRawUnsafe<InativosPorRepRow[]>(`
      SELECT inativos.cod_vendedor,
             (SELECT r.nome FROM vw_representantes r
                WHERE r.codigo = inativos.cod_vendedor
                LIMIT 1) AS nome_vendedor,
             COUNT(*)::text AS inativos_count
      FROM (
        SELECT c.id_cliente, c.cod_vendedor
        FROM vw_clientes c
        LEFT JOIN vw_pedidos_erp p
               ON p.id_cliente = c.id_cliente
              AND p.id_empresa = ${idEmpresa}
              AND p.situa     != 5
        WHERE c.ativo = 1
          ${teamSqlFilter('c.cod_vendedor')}
        GROUP BY c.id_cliente, c.cod_vendedor
        HAVING MAX(p.dt_pedido) IS NULL
            OR MAX(p.dt_pedido) < '${thirtyDaysAgo.toISOString().slice(0, 10)}'
      ) inativos
      GROUP BY inativos.cod_vendedor
      ORDER BY COUNT(*) DESC
      LIMIT 3
    `);

    // Resolve nomes de cliente e representante da fila (pedidos SAR só têm os códigos)
    const repCods = [...new Set(approvalQueue.map((p) => p.codVendedor))];
    const cliIds = [...new Set(approvalQueue.map((p) => p.idCliente))];
    const [repNameRows, cliNameRows] = await Promise.all([
      repCods.length
        ? prisma.$queryRawUnsafe<{ codigo: number; nome: string | null }[]>(
            `SELECT codigo, nome FROM vw_representantes WHERE codigo IN (${repCods.join(',')})`,
          )
        : Promise.resolve([]),
      cliIds.length
        ? prisma.$queryRawUnsafe<
            { id_cliente: number; nome: string | null; razao: string | null }[]
          >(
            `SELECT id_cliente, nome, razao FROM vw_clientes WHERE id_cliente IN (${cliIds.join(',')})`,
          )
        : Promise.resolve([]),
    ]);
    const repNameMap = new Map(repNameRows.map((r) => [Number(r.codigo), r.nome]));
    const cliNameMap = new Map(
      cliNameRows.map((c) => [Number(c.id_cliente), { nome: c.nome, razao: c.razao }]),
    );

    const mapPedido = (o: (typeof approvalQueue)[number]) => ({
      id: o.id,
      numPedSar: o.numPedSar,
      idCliente: o.idCliente,
      nomeCliente: cliNameMap.get(o.idCliente)?.nome ?? null,
      razaoCliente: cliNameMap.get(o.idCliente)?.razao ?? null,
      codVendedor: o.codVendedor,
      nomeVendedor: repNameMap.get(o.codVendedor) ?? null,
      situa: o.situa,
      dtPedido: o.dtPedido.toISOString(),
      total: String(o.total),
      descontoPerc: String(o.descontoPerc),
      obs: o.obs,
      createdAt: o.createdAt.toISOString(),
      fonte: 'sar' as const,
    });

    return {
      approvalQueue: approvalQueue.map(mapPedido),
      pedidosDia: {
        count: Number(todayRows[0]?.count ?? 0),
        total: Number(todayRows[0]?.total ?? 0),
        countSemanaAnterior: Number(lastWeekRows[0]?.count ?? 0),
        totalSemanaAnterior: Number(lastWeekRows[0]?.total ?? 0),
      },
      inativosPorRep: inativosPorRep.map((r) => ({
        codVendedor: Number(r.cod_vendedor),
        nomeVendedor: r.nome_vendedor ?? null,
        inativosCount: parseInt(r.inativos_count, 10),
      })),
      syncedAt: now.toISOString(),
    };
  }
}
