import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import type { CreateChamadoDto, AddMensagemDto, ResolverChamadoDto } from '@sar/api-interface';

type PrismaCtx = {
  chamado: {
    findMany: (args: object) => Promise<unknown[]>;
    findFirst: (args: object) => Promise<unknown | null>;
    findUnique: (args: object) => Promise<unknown | null>;
    create: (args: object) => Promise<unknown>;
    update: (args: object) => Promise<unknown>;
  };
  chamadoMensagem: { create: (args: object) => Promise<unknown> };
  $queryRawUnsafe: <T>(sql: string, ...args: unknown[]) => Promise<T[]>;
};

type ClienteRow = { id_cliente: number; nome: string | null; razao: string | null };

@Injectable()
export class SacService {
  constructor(private readonly cls: ClsService) {}

  private ctx(): { prisma: PrismaCtx; idEmpresa: number; codVendedor: number } {
    const prisma = this.cls.get('prisma') as PrismaCtx;
    if (!prisma) throw new Error('prisma não disponível no CLS');
    const idEmpresa = this.cls.get('idEmpresa') as number;
    const userId = this.cls.get('userId') as string | undefined;
    const codVendedor = userId ? parseInt(userId, 10) : 0;
    return { prisma, idEmpresa, codVendedor };
  }

  private async enrichNomes(
    prisma: PrismaCtx,
    idEmpresa: number,
    rows: { idCliente: number }[],
  ): Promise<Map<number, string>> {
    const ids = [...new Set(rows.map((r) => r.idCliente))];
    if (!ids.length) return new Map();
    const clientes = await prisma.$queryRawUnsafe<ClienteRow>(
      `SELECT id_cliente, TRIM(nome) AS nome, TRIM(razao) AS razao FROM vw_clientes WHERE id_cliente IN (${ids.join(',')}) AND id_empresa = ${idEmpresa}`,
    );
    const map = new Map<number, string>();
    for (const c of clientes) {
      map.set(c.id_cliente, c.razao ?? c.nome ?? `Cód. ${c.id_cliente}`);
    }
    return map;
  }

  async listarRep() {
    const { prisma, idEmpresa, codVendedor } = this.ctx();
    const all = (await prisma.chamado.findMany({
      where: { idEmpresa, codVendedor },
      orderBy: { updatedAt: 'desc' },
    })) as { idCliente: number; status: string; [k: string]: unknown }[];
    const nomes = await this.enrichNomes(prisma, idEmpresa, all);
    const enrich = (row: (typeof all)[0]) => ({
      ...row,
      nomeCliente:
        (row as { nomeCliente?: string | null }).nomeCliente ?? nomes.get(row.idCliente) ?? null,
    });
    return {
      abertos: all.filter((c) => !['resolvido', 'cancelado'].includes(c.status)).map(enrich),
      fechados: all.filter((c) => ['resolvido', 'cancelado'].includes(c.status)).map(enrich),
    };
  }

  // Códigos da equipe do supervisor (cod_supervisor em vw_representantes),
  // incluindo o próprio. Retorna null para gerente/admin (sem filtro).
  private async teamCodes(): Promise<number[] | null> {
    const role = (this.cls.get('role') as string | undefined) ?? 'rep';
    if (role !== 'supervisor') return null;
    const { prisma, codVendedor } = this.ctx();
    const rows = await prisma.$queryRawUnsafe<{ codigo: number }>(
      `SELECT DISTINCT codigo FROM vw_representantes WHERE cod_supervisor = ${codVendedor}`,
    );
    return [...new Set([codVendedor, ...rows.map((r) => Number(r.codigo))])];
  }

  async listarEmpresa() {
    const { prisma, idEmpresa } = this.ctx();
    // Supervisor vê só os chamados da sua equipe; gerente/admin veem a empresa toda
    const team = await this.teamCodes();
    const all = (await prisma.chamado.findMany({
      where: { idEmpresa, ...(team ? { codVendedor: { in: team } } : {}) },
      orderBy: { updatedAt: 'desc' },
    })) as { idCliente: number; status: string; [k: string]: unknown }[];
    const nomes = await this.enrichNomes(prisma, idEmpresa, all);
    const enrich = (row: (typeof all)[0]) => ({
      ...row,
      nomeCliente:
        (row as { nomeCliente?: string | null }).nomeCliente ?? nomes.get(row.idCliente) ?? null,
    });
    return {
      abertos: all.filter((c) => !['resolvido', 'cancelado'].includes(c.status)).map(enrich),
      fechados: all.filter((c) => ['resolvido', 'cancelado'].includes(c.status)).map(enrich),
    };
  }

  async detalhe(id: number, role: 'rep' | 'empresa') {
    const { prisma, idEmpresa, codVendedor } = this.ctx();
    const chamado = (await prisma.chamado.findFirst({
      where: { id, idEmpresa },
      include: { mensagens: { orderBy: { createdAt: 'asc' } } },
    })) as { idCliente: number; codVendedor: number; [k: string]: unknown } | null;
    if (!chamado) throw new NotFoundException('Chamado não encontrado');
    if (role === 'rep' && chamado.codVendedor !== codVendedor) throw new ForbiddenException();
    if (role === 'empresa') {
      const team = await this.teamCodes();
      if (team && !team.includes(chamado.codVendedor)) throw new ForbiddenException();
    }
    const nomes = await this.enrichNomes(prisma, idEmpresa, [chamado]);
    return { ...chamado, nomeCliente: nomes.get(chamado.idCliente) ?? null };
  }

  async criar(dto: CreateChamadoDto) {
    const { prisma, idEmpresa, codVendedor } = this.ctx();
    const chamado = (await prisma.chamado.create({
      data: {
        idEmpresa,
        codVendedor,
        idPedido: dto.idPedido ?? null,
        numPedSar: dto.numPedSar ?? null,
        numPedErp: dto.numPedErp ?? null,
        nomeCliente: dto.nomeCliente ?? null,
        idCliente: dto.idCliente,
        tipo: dto.tipo,
        assunto: dto.assunto,
        descricao: dto.descricao,
        itensAfetados: dto.itensAfetados ?? [],
      },
      include: { mensagens: true },
    })) as { idCliente: number; nomeCliente: string | null; [k: string]: unknown };
    const nomes = await this.enrichNomes(prisma, idEmpresa, [chamado]);
    return { ...chamado, nomeCliente: chamado.nomeCliente ?? nomes.get(chamado.idCliente) ?? null };
  }

  async addMensagemRep(id: number, dto: AddMensagemDto) {
    const { prisma, idEmpresa, codVendedor } = this.ctx();
    const c = (await prisma.chamado.findFirst({ where: { id, idEmpresa } })) as {
      codVendedor: number;
    } | null;
    if (!c) throw new NotFoundException();
    if (c.codVendedor !== codVendedor) throw new ForbiddenException();
    await prisma.chamadoMensagem.create({
      data: { idChamado: id, autor: 'rep', texto: dto.texto },
    });
    await prisma.chamado.update({ where: { id }, data: { status: 'em_analise' } });
    return this.detalhe(id, 'rep');
  }

  async addMensagemEmpresa(id: number, dto: AddMensagemDto) {
    const { prisma, idEmpresa } = this.ctx();
    const c = await prisma.chamado.findFirst({ where: { id, idEmpresa } });
    if (!c) throw new NotFoundException();
    await prisma.chamadoMensagem.create({
      data: { idChamado: id, autor: 'empresa', texto: dto.texto },
    });
    await prisma.chamado.update({ where: { id }, data: { status: 'aguardando_rep' } });
    return this.detalhe(id, 'empresa');
  }

  async resolver(id: number, dto: ResolverChamadoDto) {
    const { prisma, idEmpresa } = this.ctx();
    const c = await prisma.chamado.findFirst({ where: { id, idEmpresa } });
    if (!c) throw new NotFoundException();
    if (dto.mensagemFinal) {
      await prisma.chamadoMensagem.create({
        data: { idChamado: id, autor: 'empresa', texto: dto.mensagemFinal },
      });
    }
    await prisma.chamado.update({
      where: { id },
      data: {
        status: 'resolvido',
        resolucao: dto.resolucao,
        notaResolucao: dto.notaResolucao ?? null,
      },
    });
    return this.detalhe(id, 'empresa');
  }

  async cancelarRep(id: number) {
    const { prisma, idEmpresa, codVendedor } = this.ctx();
    const c = (await prisma.chamado.findFirst({ where: { id, idEmpresa } })) as {
      codVendedor: number;
      status: string;
    } | null;
    if (!c) throw new NotFoundException();
    if (c.codVendedor !== codVendedor) throw new ForbiddenException();
    if (['resolvido', 'cancelado'].includes(c.status))
      throw new ForbiddenException('Chamado já encerrado');
    await prisma.chamado.update({ where: { id }, data: { status: 'cancelado' } });
    return this.detalhe(id, 'rep');
  }
}
