import { Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import type {
  ReportMetaQuery,
  ReportMetaResponse,
  ReportCarteiraResponse,
  ReportAbcQuery,
  ReportAbcResponse,
  AbcProduto,
} from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types';
import { getTeamCodes } from '../workspace/team-scope.util';

function d(v: unknown): string {
  return v != null ? String(v) : '0';
}

function n(v: unknown): number {
  return v != null ? Number(v) : 0;
}

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

  async metaVsRealizado(query: ReportMetaQuery): Promise<ReportMetaResponse> {
    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 userId = this.cls.get('userId');
    const codVendedor = userId ? parseInt(userId, 10) : 0;
    const { mes, ano } = query;

    interface RealizadoRow {
      realizado: unknown;
      comissao: unknown;
      total_pedidos: unknown;
    }
    const [realizadoRows, metaRows] = await Promise.all([
      prisma.$queryRawUnsafe<RealizadoRow[]>(`
        SELECT
          COALESCE(SUM(total), 0)   AS realizado,
          COALESCE(SUM(comissao), 0) AS comissao,
          COUNT(id)                  AS total_pedidos
        FROM sar.pedidos
        WHERE id_empresa    = ${idEmpresa}
          AND cod_vendedor  = ${codVendedor}
          AND situa         IN (2, 4)
          AND EXTRACT(MONTH FROM dt_pedido) = ${mes}
          AND EXTRACT(YEAR  FROM dt_pedido) = ${ano}
      `),
      prisma.metaRepresentante.findUnique({
        where: { codVendedor_idEmpresa_ano_mes: { codVendedor, idEmpresa, ano, mes } },
      }),
    ]);

    const realizado = n(realizadoRows[0]?.realizado);
    const comissao = n(realizadoRows[0]?.comissao);
    const totalPedidos = n(realizadoRows[0]?.total_pedidos);
    const meta = metaRows ? Number(metaRows.metaValor) : 0;
    const percentual = meta > 0 ? (realizado / meta) * 100 : 0;

    return {
      mes,
      ano,
      realizado: realizado.toFixed(2),
      meta: meta.toFixed(2),
      percentual: percentual.toFixed(2),
      comissaoEstimada: comissao.toFixed(2),
      totalPedidos,
    };
  }

  async carteira(): Promise<ReportCarteiraResponse> {
    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 userId = this.cls.get('userId');
    const codVendedor = userId ? parseInt(userId, 10) : 0;

    // Rep vê a própria carteira; supervisor vê as carteiras da equipe
    const role = this.cls.get('role');
    const team = role === 'supervisor' ? await getTeamCodes(prisma, codVendedor) : null;
    const vendCond = (col: string) =>
      team ? `${col} IN (${team.join(',')})` : `${col} = ${codVendedor}`;

    interface CarteiraRow {
      id_cliente: unknown;
      nome: string | null;
      razao: string | null;
      ativo: unknown;
      ultimo_pedido: string | null;
      dias_sem_pedido: unknown;
      total_pedidos: unknown;
      ticket_medio: unknown;
      faturamento_total: unknown;
    }

    const rows = await prisma.$queryRawUnsafe<CarteiraRow[]>(`
      WITH ultimos AS (
        SELECT
          id_cliente,
          MAX(dt_pedido)                              AS ultimo_pedido,
          COUNT(*)                                    AS total_pedidos,
          CASE WHEN COUNT(*) > 0
               THEN SUM(total)::numeric / COUNT(*)
               ELSE 0
          END                                         AS ticket_medio,
          COALESCE(SUM(total), 0)::numeric            AS faturamento_total
        FROM vw_pedidos_erp
        WHERE id_empresa   = ${idEmpresa}
          AND ${vendCond('cod_vendedor')}
          AND situa        NOT IN (1, 5)
        GROUP BY id_cliente
      )
      SELECT
        c.id_cliente,
        TRIM(c.nome)                                   AS nome,
        TRIM(c.razao)                                   AS razao,
        c.ativo,
        TO_CHAR(u.ultimo_pedido, 'YYYY-MM-DD')          AS ultimo_pedido,
        (CURRENT_DATE - u.ultimo_pedido::date)          AS dias_sem_pedido,
        COALESCE(u.total_pedidos, 0)::int               AS total_pedidos,
        COALESCE(u.ticket_medio, 0)::numeric            AS ticket_medio,
        COALESCE(u.faturamento_total, 0)::numeric       AS faturamento_total
      FROM vw_clientes c
      LEFT JOIN ultimos u ON u.id_cliente = c.id_cliente
      WHERE ${vendCond('c.cod_vendedor')}
        AND c.ativo        = 1
      ORDER BY u.faturamento_total DESC NULLS LAST, c.nome ASC
    `);

    // Deduplica por id_cliente — vw_clientes pode retornar o mesmo cliente em mais de uma empresa.
    const seen = new Set<number>();
    const unique = rows.filter((r) => {
      const id = n(r.id_cliente);
      if (seen.has(id)) return false;
      seen.add(id);
      return true;
    });

    const faturamentoRepTotal = unique.reduce((acc, r) => acc + n(r.faturamento_total), 0);

    const data = unique.map((r) => {
      const fat = n(r.faturamento_total);
      return {
        idCliente: n(r.id_cliente),
        nome: r.nome ?? null,
        razao: r.razao ?? null,
        ativo: n(r.ativo),
        ultimoPedido: r.ultimo_pedido ?? null,
        diasSemPedido: r.dias_sem_pedido != null ? n(r.dias_sem_pedido) : null,
        totalPedidos: n(r.total_pedidos),
        ticketMedio: d(r.ticket_medio),
        faturamentoTotal: fat.toFixed(2),
        participacaoPct:
          faturamentoRepTotal > 0 ? Math.round((fat / faturamentoRepTotal) * 1000) / 10 : 0,
      };
    });

    const inativos30 = data.filter((c) => c.diasSemPedido != null && c.diasSemPedido >= 30).length;
    const inativos60 = data.filter((c) => c.diasSemPedido != null && c.diasSemPedido >= 60).length;
    const semPedido = data.filter((c) => c.totalPedidos === 0).length;

    return {
      data,
      total: data.length,
      inativos30,
      inativos60,
      semPedido,
      faturamentoRepTotal: faturamentoRepTotal.toFixed(2),
    };
  }

  async abcProdutos(query: ReportAbcQuery): Promise<ReportAbcResponse> {
    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 userId = this.cls.get('userId');
    const codVendedor = userId ? parseInt(userId, 10) : 0;
    const { mes, ano } = query;

    const mesFilter = mes != null ? `AND EXTRACT(MONTH FROM p.dt_pedido) = ${mes}` : '';
    const anoFilter = ano != null ? `AND EXTRACT(YEAR  FROM p.dt_pedido) = ${ano}` : '';

    // Rep vê os próprios pedidos; supervisor vê os da equipe
    const role = this.cls.get('role');
    const team = role === 'supervisor' ? await getTeamCodes(prisma, codVendedor) : null;
    const vendCond = team
      ? `p.cod_vendedor IN (${team.join(',')})`
      : `p.cod_vendedor = ${codVendedor}`;

    interface AbcRow {
      cod_produto: string | null;
      desc_produto: string | null;
      total_faturado: unknown;
      qtd_vendida: unknown;
      desconto_medio: unknown;
      comissao_total: unknown;
    }

    const rows = await prisma.$queryRawUnsafe<AbcRow[]>(`
      SELECT
        pi.cod_produto,
        pi.desc_produto,
        SUM(pi.total)        AS total_faturado,
        SUM(pi.qtd)          AS qtd_vendida,
        AVG(pi.desconto_perc) AS desconto_medio,
        SUM(pi.comissao)     AS comissao_total
      FROM sar.pedido_itens pi
      JOIN sar.pedidos p ON p.id = pi.id_pedido
      WHERE p.id_empresa   = ${idEmpresa}
        AND ${vendCond}
        AND p.situa        NOT IN (0, 3)
        ${mesFilter}
        ${anoFilter}
      GROUP BY pi.cod_produto, pi.desc_produto
      ORDER BY total_faturado DESC
    `);

    const totalGeral = rows.reduce((s, r) => s + n(r.total_faturado), 0);

    let acumulado = 0;
    const data: AbcProduto[] = rows.map((r) => {
      const total = n(r.total_faturado);
      const participacao = totalGeral > 0 ? (total / totalGeral) * 100 : 0;
      acumulado += participacao;
      const curva: 'A' | 'B' | 'C' = acumulado <= 80 ? 'A' : acumulado <= 95 ? 'B' : 'C';
      return {
        codProduto: r.cod_produto ?? null,
        descProduto: r.desc_produto ?? '(sem descrição)',
        totalFaturado: total.toFixed(2),
        qtdVendida: d(r.qtd_vendida),
        participacao: participacao.toFixed(2),
        curva,
        descontoMedio: n(r.desconto_medio).toFixed(2),
        comissaoTotal: n(r.comissao_total).toFixed(2),
      };
    });

    const meses = [
      'Jan',
      'Fev',
      'Mar',
      'Abr',
      'Mai',
      'Jun',
      'Jul',
      'Ago',
      'Set',
      'Out',
      'Nov',
      'Dez',
    ];
    const periodo =
      mes != null && ano != null
        ? `${meses[mes - 1]}/${ano}`
        : ano != null
          ? String(ano)
          : 'Acumulado';

    return { data, totalFaturado: totalGeral.toFixed(2), periodo };
  }
}
