import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import type {
  AlcadaDescontosResponse,
  UpsertDescontoBody,
  PromocoesResponse,
  CreatePromocaoBody,
  UpdatePromocaoBody,
  RegrasDescontoResponse,
  CreateRegraDescontoBody,
  UpdateRegraDescontoBody,
  RegrasVigentesResponse,
  PromocoesVigentesResponse,
  AlcadaRepResponse,
  GruposProdutoResponse,
} from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types';

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

  private assertManager(): void {
    const role = this.cls.get('role') ?? 'rep';
    if (role !== 'manager' && role !== 'admin') {
      throw new ForbiddenException('Apenas gerentes podem gerenciar políticas comerciais');
    }
  }

  async listDescontos(): Promise<AlcadaDescontosResponse> {
    this.assertManager();
    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 rows = await prisma.alcadaDesconto.findMany({
      where: { idEmpresa },
      orderBy: [{ codVendedor: 'asc' }, { codGrupo: 'asc' }],
    });

    // Busca nomes dos representantes
    const codigos = [...new Set(rows.map((r) => r.codVendedor))];
    const repRows =
      codigos.length > 0
        ? await prisma.$queryRawUnsafe<{ codigo: number; nome: string | null }[]>(
            `SELECT codigo, nome FROM vw_representantes WHERE codigo IN (${codigos.join(',')})`,
          )
        : [];
    const repNameMap = new Map(repRows.map((r) => [Number(r.codigo), r.nome]));

    return {
      descontos: rows.map((r) => ({
        codVendedor: r.codVendedor,
        codGrupo: r.codGrupo,
        limitePerc: Number(r.limitePerc),
        nomeVendedor: repNameMap.get(r.codVendedor) ?? null,
      })),
    };
  }

  async upsertDesconto(body: UpsertDescontoBody): Promise<void> {
    this.assertManager();
    const prisma = this.cls.get('prisma');
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa');

    await prisma.alcadaDesconto.upsert({
      where: {
        codVendedor_idEmpresa_codGrupo: {
          codVendedor: body.codVendedor,
          idEmpresa,
          codGrupo: body.codGrupo ?? 0,
        },
      },
      create: {
        codVendedor: body.codVendedor,
        idEmpresa,
        codGrupo: body.codGrupo ?? 0,
        limitePerc: body.limitePerc,
      },
      update: { limitePerc: body.limitePerc },
    });
  }

  async listPromocoes(): Promise<PromocoesResponse> {
    this.assertManager();
    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 rows = await prisma.promocao.findMany({
      where: { idEmpresa },
      orderBy: [{ dataFim: 'desc' }, { id: 'desc' }],
    });

    return {
      promocoes: rows.map((p) => ({
        id: p.id,
        descricao: p.descricao,
        codProduto: p.codProduto,
        grpProd: p.grpProd,
        descPct: Number(p.descPct),
        dataInicio: p.dataInicio.toISOString().slice(0, 10),
        dataFim: p.dataFim.toISOString().slice(0, 10),
        ativa: p.ativa,
        createdAt: p.createdAt.toISOString(),
        createdBy: p.createdBy,
      })),
    };
  }

  async createPromocao(body: CreatePromocaoBody): Promise<{ id: number }> {
    this.assertManager();
    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') ?? 'system';

    const p = await prisma.promocao.create({
      data: {
        idEmpresa,
        descricao: body.descricao,
        codProduto: body.codProduto ?? null,
        grpProd: body.grpProd ?? null,
        descPct: body.descPct,
        dataInicio: new Date(body.dataInicio),
        dataFim: new Date(body.dataFim),
        createdBy: userId,
      },
    });
    return { id: p.id };
  }

  async updatePromocao(id: number, body: UpdatePromocaoBody): Promise<void> {
    this.assertManager();
    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 exists = await prisma.promocao.findFirst({ where: { id, idEmpresa } });
    if (!exists) throw new NotFoundException('Promoção não encontrada');

    await prisma.promocao.update({
      where: { id },
      data: {
        ...(body.descricao !== undefined && { descricao: body.descricao }),
        ...(body.codProduto !== undefined && { codProduto: body.codProduto }),
        ...(body.grpProd !== undefined && { grpProd: body.grpProd }),
        ...(body.descPct !== undefined && { descPct: body.descPct }),
        ...(body.dataInicio !== undefined && { dataInicio: new Date(body.dataInicio) }),
        ...(body.dataFim !== undefined && { dataFim: new Date(body.dataFim) }),
        ...(body.ativa !== undefined && { ativa: body.ativa }),
      },
    });
  }

  async deletePromocao(id: number): Promise<void> {
    this.assertManager();
    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 exists = await prisma.promocao.findFirst({ where: { id, idEmpresa } });
    if (!exists) throw new NotFoundException('Promoção não encontrada');

    await prisma.promocao.delete({ where: { id } });
  }

  // ─── Regras de Desconto ─────────────────────────────────────────────────────

  // Grupos/subgrupos distintos de vw_produtos (empresa matriz), para combos e
  // resolução de nomes na listagem — UI sempre mostra o nome, nunca só o código.
  private async loadGruposProduto(): Promise<
    {
      cod_grupo: number | null;
      grupo: string | null;
      cod_subgrupo: number | null;
      subgrupo: string | null;
    }[]
  > {
    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 idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;

    return prisma.$queryRawUnsafe(
      `SELECT DISTINCT cod_grupo, TRIM(grupo) AS grupo, cod_subgrupo, TRIM(subgrupo) AS subgrupo
         FROM vw_produtos
        WHERE id_empresa = ${idMatriz}
          AND (cod_grupo IS NOT NULL OR cod_subgrupo IS NOT NULL)
        ORDER BY grupo, subgrupo`,
    );
  }

  async listGruposProduto(): Promise<GruposProdutoResponse> {
    this.assertManager();
    const rows = await this.loadGruposProduto();
    return {
      grupos: rows.map((r) => ({
        codGrupo: r.cod_grupo != null ? Number(r.cod_grupo) : null,
        grupo: r.grupo,
        codSubgrupo: r.cod_subgrupo != null ? Number(r.cod_subgrupo) : null,
        subgrupo: r.subgrupo,
      })),
    };
  }

  async listRegras(): Promise<RegrasDescontoResponse> {
    this.assertManager();
    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 rows = await prisma.regraDesconto.findMany({
      where: { idEmpresa },
      orderBy: [{ dataFim: 'desc' }, { id: 'desc' }],
    });

    const codigos = [
      ...new Set(rows.map((r) => r.codVendedor).filter((c): c is number => c != null)),
    ];
    const repRows =
      codigos.length > 0
        ? await prisma.$queryRawUnsafe<{ codigo: number; nome: string | null }[]>(
            `SELECT codigo, nome FROM vw_representantes WHERE codigo IN (${codigos.join(',')})`,
          )
        : [];
    const repNameMap = new Map(repRows.map((r) => [Number(r.codigo), r.nome]));

    const grupos = rows.some((r) => r.codGrupo != null || r.codSubgrupo != null)
      ? await this.loadGruposProduto()
      : [];
    const grupoNameMap = new Map(
      grupos.filter((g) => g.cod_grupo != null).map((g) => [Number(g.cod_grupo), g.grupo]),
    );
    const subgrupoNameMap = new Map(
      grupos.filter((g) => g.cod_subgrupo != null).map((g) => [Number(g.cod_subgrupo), g.subgrupo]),
    );

    return {
      regras: rows.map((r) => ({
        id: r.id,
        descricao: r.descricao,
        codGrupo: r.codGrupo,
        grupo: r.codGrupo != null ? (grupoNameMap.get(r.codGrupo) ?? null) : null,
        codSubgrupo: r.codSubgrupo,
        subgrupo: r.codSubgrupo != null ? (subgrupoNameMap.get(r.codSubgrupo) ?? null) : null,
        codVendedor: r.codVendedor,
        nomeVendedor: r.codVendedor != null ? (repNameMap.get(r.codVendedor) ?? null) : null,
        descPct: Number(r.descPct),
        dataInicio: r.dataInicio.toISOString().slice(0, 10),
        dataFim: r.dataFim.toISOString().slice(0, 10),
        ativa: r.ativa,
        createdAt: r.createdAt.toISOString(),
        createdBy: r.createdBy,
      })),
    };
  }

  async createRegra(body: CreateRegraDescontoBody): Promise<{ id: number }> {
    this.assertManager();
    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') ?? 'system';

    const r = await prisma.regraDesconto.create({
      data: {
        idEmpresa,
        descricao: body.descricao,
        codGrupo: body.codGrupo ?? null,
        codSubgrupo: body.codSubgrupo ?? null,
        codVendedor: body.codVendedor ?? null,
        descPct: body.descPct,
        dataInicio: new Date(body.dataInicio),
        dataFim: new Date(body.dataFim),
        createdBy: userId,
      },
    });
    return { id: r.id };
  }

  async updateRegra(id: number, body: UpdateRegraDescontoBody): Promise<void> {
    this.assertManager();
    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 exists = await prisma.regraDesconto.findFirst({ where: { id, idEmpresa } });
    if (!exists) throw new NotFoundException('Regra de desconto não encontrada');

    await prisma.regraDesconto.update({
      where: { id },
      data: {
        ...(body.descricao !== undefined && { descricao: body.descricao }),
        ...(body.codGrupo !== undefined && { codGrupo: body.codGrupo }),
        ...(body.codSubgrupo !== undefined && { codSubgrupo: body.codSubgrupo }),
        ...(body.codVendedor !== undefined && { codVendedor: body.codVendedor }),
        ...(body.descPct !== undefined && { descPct: body.descPct }),
        ...(body.dataInicio !== undefined && { dataInicio: new Date(body.dataInicio) }),
        ...(body.dataFim !== undefined && { dataFim: new Date(body.dataFim) }),
        ...(body.ativa !== undefined && { ativa: body.ativa }),
      },
    });
  }

  async deleteRegra(id: number): Promise<void> {
    this.assertManager();
    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 exists = await prisma.regraDesconto.findFirst({ where: { id, idEmpresa } });
    if (!exists) throw new NotFoundException('Regra de desconto não encontrada');

    await prisma.regraDesconto.delete({ where: { id } });
  }

  // Alçada do próprio usuário logado — o carrinho usa para mostrar o teto de
  // desconto por item (codGrupo 0 = default; sem linha 0, o backend assume 5%).
  async minhaAlcada(): Promise<AlcadaRepResponse> {
    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(this.cls.get('userId') ?? '0', 10);

    const rows = await prisma.alcadaDesconto.findMany({
      where: { codVendedor, idEmpresa },
    });
    return {
      limites: rows.map((r) => ({
        codGrupo: r.codGrupo,
        limitePerc: Number(r.limitePerc),
      })),
    };
  }

  // Promoções vigentes hoje — qualquer usuário autenticado. Usadas pelo rep para
  // sinalizar no catálogo que o produto tem condição comercial diferenciada.
  async listPromocoesVigentes(): Promise<PromocoesVigentesResponse> {
    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 idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;

    const hoje = new Date();
    const rows = await prisma.promocao.findMany({
      where: {
        idEmpresa: { in: [idEmpresa, idMatriz] },
        ativa: true,
        dataInicio: { lte: hoje },
        dataFim: { gte: hoje },
      },
      orderBy: [{ descPct: 'desc' }],
    });

    return {
      promocoes: rows.map((p) => ({
        id: p.id,
        descricao: p.descricao,
        codProduto: p.codProduto,
        grpProd: p.grpProd,
        descPct: Number(p.descPct),
        dataFim: p.dataFim.toISOString().slice(0, 10),
      })),
    };
  }

  // Regras vigentes hoje para o usuário logado. Rep recebe só as regras que o
  // alcançam (cod_vendedor nulo = todos, ou o próprio código); gerente vê todas.
  async listRegrasVigentes(): Promise<RegrasVigentesResponse> {
    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 idMatriz = idEmpresa > 9000 ? idEmpresa - 9000 : idEmpresa;
    const role = this.cls.get('role') ?? 'rep';
    const codVendedor = parseInt(this.cls.get('userId') ?? '0', 10);

    const hoje = new Date();
    const rows = await prisma.regraDesconto.findMany({
      where: {
        idEmpresa: { in: [idEmpresa, idMatriz] },
        ativa: true,
        dataInicio: { lte: hoje },
        dataFim: { gte: hoje },
        ...(role === 'rep' ? { OR: [{ codVendedor: null }, { codVendedor }] } : {}),
      },
      orderBy: [{ descPct: 'desc' }],
    });

    return {
      regras: rows.map((r) => ({
        id: r.id,
        descricao: r.descricao,
        codGrupo: r.codGrupo,
        codSubgrupo: r.codSubgrupo,
        descPct: Number(r.descPct),
        dataFim: r.dataFim.toISOString().slice(0, 10),
      })),
    };
  }
}
