import { BadGatewayException, BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { envs } from '../config/envs';
import { AuditService } from '../audit/audit.service';
import { PrismaService } from '../prisma/prisma.service';
import { ContabilidadIntegracionService } from '../contabilidad/services/integracion.service';
import { AsignarEquipoDto } from './dto/asignar-equipo.dto';
import { CreateSupervisorDto } from './dto/create-supervisor.dto';
import { GenerarComisionSupervisorDto } from './dto/generar-comision.dto';
import { UpdateSupervisorDto } from './dto/update-supervisor.dto';

@Injectable()
export class SupervisoresService {
  private readonly logger = new Logger(SupervisoresService.name);
  private readonly kudeUrl = envs.apiGeneradorPDF;

  constructor(
    private readonly prisma: PrismaService,
    private readonly contabilidad: ContabilidadIntegracionService,
    private readonly auditService: AuditService,
  ) {}

  private async callMsvKude(endpoint: string, payload: Record<string, unknown>): Promise<Buffer> {
    const url = `${this.kudeUrl}/api/contabilidad/${endpoint}`;
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...payload, tipo: 'view' }),
      });
      if (!response.ok) {
        const err = await response.text();
        this.logger.error(`msv-kude ${url} → ${response.status}: ${err}`);
        throw new BadGatewayException('Error generando PDF en msv-kude');
      }
      const buf = await response.arrayBuffer();
      return Buffer.from(buf);
    } catch (e) {
      if (e instanceof BadGatewayException) throw e;
      this.logger.error(`msv-kude fetch error: ${(e as Error).message}`);
      throw new BadGatewayException('No se pudo contactar al servicio de PDFs');
    }
  }

  private async getEmpresaHeader(empresaId: string) {
    const e = await this.prisma.empresas.findUnique({
      where: { id: empresaId },
      select: { razon_social: true, nombre_fantasia: true, ruc: true, logo: true },
    });
    // nombre_fantasia: el PDF lo pone arriba y la razón social debajo (mismo criterio que la factura).
    return {
      razon_social: e?.razon_social ?? '',
      nombre_fantasia: e?.nombre_fantasia ?? null,
      ruc: e?.ruc ?? '',
      logo_url: e?.logo ?? null,
    };
  }

  private formatPeriodo(desde?: Date, hasta?: Date) {
    const f = (d: Date) => d.toLocaleDateString('es-PY', { day: '2-digit', month: '2-digit', year: 'numeric' });
    if (desde && hasta) return `${f(desde)} — ${f(hasta)}`;
    if (desde) return `Desde ${f(desde)}`;
    if (hasta) return `Hasta ${f(hasta)}`;
    return 'Todos los períodos';
  }

  async create(empresa_id: string, dto: CreateSupervisorDto) {
    return this.prisma.supervisores.create({
      data: { ...dto, empresa_id },
      include: { _count: { select: { equipo: true } } },
    });
  }

  async findAll(empresa_id: string, opts: { active?: boolean; search?: string } = {}) {
    const where: any = { empresa_id };
    if (opts.active !== undefined) where.active = opts.active;
    if (opts.search) {
      where.OR = [
        { nombre: { contains: opts.search, mode: 'insensitive' } },
        { apellido: { contains: opts.search, mode: 'insensitive' } },
        { documento: { contains: opts.search, mode: 'insensitive' } },
        { codigo: { contains: opts.search, mode: 'insensitive' } },
      ];
    }

    const [supervisores, total] = await Promise.all([
      this.prisma.supervisores.findMany({
        where,
        include: {
          _count: { select: { equipo: true } },
        },
        orderBy: { nombre: 'asc' },
      }),
      this.prisma.supervisores.count({ where }),
    ]);

    const stats = {
      total,
      activos: await this.prisma.supervisores.count({ where: { empresa_id, active: true } }),
    };

    return { supervisores, stats };
  }

  async findOne(empresa_id: string, id: string) {
    const supervisor = await this.prisma.supervisores.findFirst({
      where: { id, empresa_id },
      include: {
        equipo: {
          include: {
            vendedor_cobrador: {
              select: { id: true, nombre: true, apellido: true, tipo: true, codigo: true, active: true },
            },
          },
        },
      },
    });
    if (!supervisor) throw new NotFoundException('Supervisor no encontrado');
    return supervisor;
  }

  async findActivos(empresa_id: string) {
    return this.prisma.supervisores.findMany({
      where: { empresa_id, active: true },
      select: { id: true, nombre: true, apellido: true, codigo: true, porcentaje_comision_ventas: true, porcentaje_comision_cobros: true },
      orderBy: { nombre: 'asc' },
    });
  }

  async update(empresa_id: string, id: string, dto: UpdateSupervisorDto) {
    await this.findOne(empresa_id, id);
    return this.prisma.supervisores.update({
      where: { id },
      data: { ...dto, updated_at: new Date() },
      include: { _count: { select: { equipo: true } } },
    });
  }

  async remove(empresa_id: string, id: string) {
    await this.findOne(empresa_id, id);
    return this.prisma.supervisores.update({
      where: { id },
      data: { active: false, updated_at: new Date() },
    });
  }

  // --- Gestión de equipo ---

  async getEquipo(empresa_id: string, supervisorId: string) {
    await this.findOne(empresa_id, supervisorId);
    return this.prisma.supervisor_vendedores_cobradores.findMany({
      where: { supervisor_id: supervisorId },
      include: {
        vendedor_cobrador: {
          select: { id: true, nombre: true, apellido: true, tipo: true, codigo: true, active: true, comision_venta: true, comision_cobranza: true },
        },
      },
    });
  }

  async asignarEquipo(empresa_id: string, supervisorId: string, dto: AsignarEquipoDto) {
    await this.findOne(empresa_id, supervisorId);

    await this.prisma.supervisor_vendedores_cobradores.createMany({
      data: dto.vendedor_cobrador_ids.map((vcId) => ({
        supervisor_id: supervisorId,
        vendedor_cobrador_id: vcId,
      })),
      skipDuplicates: true,
    });

    return this.getEquipo(empresa_id, supervisorId);
  }

  async desasignarEquipo(empresa_id: string, supervisorId: string, vendedorCobradorId: string) {
    await this.findOne(empresa_id, supervisorId);
    await this.prisma.supervisor_vendedores_cobradores.delete({
      where: { supervisor_id_vendedor_cobrador_id: { supervisor_id: supervisorId, vendedor_cobrador_id: vendedorCobradorId } },
    });
    return { message: 'Desasignado correctamente' };
  }

  // --- Comisiones ---

  /**
   * Calcula totales del supervisor desde la tabla `comisiones`, filtrando por la
   * fecha del documento origen (factura.dfeemide / recibo.fecha_emision).
   *
   * Regla de atribución para VENTAS (la dirección manda):
   *   1) Si la factura tiene `cliente_direccion.supervisor_id` → cuenta SIEMPRE
   *      para ese supervisor (sin importar a qué equipo pertenezca el vendedor).
   *   2) Si la dirección no tiene supervisor (o no hay dirección) → cuenta para
   *      el supervisor del equipo al que pertenece el vendedor.
   *
   * Para COBRANZAS no hay dirección asociada al recibo → se usa solo el equipo.
   */
  private async calcularTotalesEquipo(
    empresa_id: string,
    supervisorId: string,
    equipoIds: string[],
    desde: Date,
    hasta: Date,
  ) {
    const comisionesVentas = await this.prisma.comisiones.findMany({
      where: {
        empresa_id,
        tipo: 'venta',
        estado: { notIn: ['cancelada', 'anulada'] },
        factura_cab: { dfeemide: { gte: desde, lte: hasta } },
        OR: [
          // 1) Dirección asignada a este supervisor (manda sobre el equipo)
          { factura_cab: { cliente_direccion: { supervisor_id: supervisorId } } },
          // 2) Sin supervisor en dirección + vendedor del equipo (de la comisión)
          {
            vendedor_cobrador_id: { in: equipoIds },
            factura_cab: {
              OR: [
                { cliente_direccion_id: null },
                { cliente_direccion: { supervisor_id: null } },
              ],
            },
          },
          // 3) Sin supervisor en dirección + vendedor de la FACTURA del equipo
          {
            factura_cab: {
              vendedor_id: { in: equipoIds },
              OR: [
                { cliente_direccion_id: null },
                { cliente_direccion: { supervisor_id: null } },
              ],
            },
          },
        ],
      },
      select: { vendedor_cobrador_id: true, monto_base: true },
    });

    const comisionesCobros = await this.prisma.comisiones.findMany({
      where: {
        empresa_id,
        tipo: 'cobranza',
        vendedor_cobrador_id: { in: equipoIds },
        estado: { notIn: ['cancelada', 'anulada'] },
        recibos_cobro: {
          fecha_emision: { gte: desde, lte: hasta },
          estado: { not: 'anulado' },
        },
      },
      select: { vendedor_cobrador_id: true, monto_base: true },
    });

    const ventasPorVc = new Map<string, number>();
    const cobrosPorVc = new Map<string, number>();
    for (const c of comisionesVentas) {
      ventasPorVc.set(c.vendedor_cobrador_id, (ventasPorVc.get(c.vendedor_cobrador_id) ?? 0) + Number(c.monto_base ?? 0));
    }
    for (const c of comisionesCobros) {
      cobrosPorVc.set(c.vendedor_cobrador_id, (cobrosPorVc.get(c.vendedor_cobrador_id) ?? 0) + Number(c.monto_base ?? 0));
    }

    const totalVentas = Array.from(ventasPorVc.values()).reduce((s, n) => s + n, 0);
    const totalCobros = Array.from(cobrosPorVc.values()).reduce((s, n) => s + n, 0);

    return { totalVentas, totalCobros, ventasPorVc, cobrosPorVc };
  }

  async generarComision(empresa_id: string, supervisorId: string, dto: GenerarComisionSupervisorDto) {
    const supervisor = await this.findOne(empresa_id, supervisorId);

    const equipoIds = supervisor.equipo.map((e) => e.vendedor_cobrador_id);
    if (equipoIds.length === 0) throw new BadRequestException('El supervisor no tiene vendedores/cobradores asignados');

    const desde = new Date(dto.periodo_desde);
    const hasta = new Date(dto.periodo_hasta);
    hasta.setHours(23, 59, 59, 999);

    const { totalVentas, totalCobros } = await this.calcularTotalesEquipo(empresa_id, supervisorId, equipoIds, desde, hasta);

    const pctVentas = Number(supervisor.porcentaje_comision_ventas ?? 0);
    const pctCobros = Number(supervisor.porcentaje_comision_cobros ?? 0);
    const comisionVentas = (totalVentas * pctVentas) / 100;
    const comisionCobros = (totalCobros * pctCobros) / 100;
    const totalComision = comisionVentas + comisionCobros;

    return this.prisma.comisiones_supervisores.create({
      data: {
        supervisor_id: supervisorId,
        empresa_id,
        periodo_desde: desde,
        periodo_hasta: hasta,
        total_ventas: totalVentas,
        total_cobros: totalCobros,
        comision_ventas: comisionVentas,
        comision_cobros: comisionCobros,
        total_comision: totalComision,
        observaciones: dto.observaciones,
      },
    });
  }

  async previewComision(
    empresa_id: string,
    supervisorId: string,
    opts: { periodo_desde: string; periodo_hasta: string },
  ) {
    const supervisor = await this.findOne(empresa_id, supervisorId);
    const equipoIds = supervisor.equipo.map((e) => e.vendedor_cobrador_id);
    if (equipoIds.length === 0) throw new BadRequestException('El supervisor no tiene vendedores/cobradores asignados');

    const desde = new Date(opts.periodo_desde);
    const hasta = new Date(opts.periodo_hasta);
    hasta.setHours(23, 59, 59, 999);

    const { totalVentas, totalCobros, ventasPorVc, cobrosPorVc } =
      await this.calcularTotalesEquipo(empresa_id, supervisorId, equipoIds, desde, hasta);

    const pctVentas = Number(supervisor.porcentaje_comision_ventas ?? 0);
    const pctCobros = Number(supervisor.porcentaje_comision_cobros ?? 0);
    const comisionVentas = (totalVentas * pctVentas) / 100;
    const comisionCobros = (totalCobros * pctCobros) / 100;

    const equipo = supervisor.equipo.map((e) => ({
      vendedor_cobrador: e.vendedor_cobrador,
      ventas: ventasPorVc.get(e.vendedor_cobrador_id) ?? 0,
      cobros: cobrosPorVc.get(e.vendedor_cobrador_id) ?? 0,
    }));

    return {
      supervisor: {
        id: supervisor.id,
        nombre: `${supervisor.nombre ?? ''} ${supervisor.apellido ?? ''}`.trim(),
        codigo: supervisor.codigo,
        porcentaje_comision_ventas: pctVentas,
        porcentaje_comision_cobros: pctCobros,
      },
      periodo: { desde, hasta },
      equipo,
      totales: {
        ventas: totalVentas,
        cobros: totalCobros,
        comision_ventas: comisionVentas,
        comision_cobros: comisionCobros,
        total_comision: comisionVentas + comisionCobros,
      },
    };
  }

  async recalcularComision(empresa_id: string, comisionId: string) {
    const comision = await this.prisma.comisiones_supervisores.findFirst({
      where: { id: comisionId, empresa_id },
    });
    if (!comision) throw new NotFoundException('Comisión no encontrada');
    if (comision.estado === 'liquidada') throw new BadRequestException('No se puede recalcular una comisión liquidada');

    const supervisor = await this.findOne(empresa_id, comision.supervisor_id);
    const equipoIds = supervisor.equipo.map((e) => e.vendedor_cobrador_id);
    if (equipoIds.length === 0) throw new BadRequestException('El supervisor no tiene vendedores/cobradores asignados');

    const desde = comision.periodo_desde;
    const hasta = new Date(comision.periodo_hasta);
    hasta.setHours(23, 59, 59, 999);

    const { totalVentas, totalCobros } = await this.calcularTotalesEquipo(empresa_id, comision.supervisor_id, equipoIds, desde, hasta);

    const pctVentas = Number(supervisor.porcentaje_comision_ventas ?? 0);
    const pctCobros = Number(supervisor.porcentaje_comision_cobros ?? 0);
    const comisionVentas = (totalVentas * pctVentas) / 100;
    const comisionCobros = (totalCobros * pctCobros) / 100;

    return this.prisma.comisiones_supervisores.update({
      where: { id: comisionId },
      data: {
        total_ventas: totalVentas,
        total_cobros: totalCobros,
        comision_ventas: comisionVentas,
        comision_cobros: comisionCobros,
        total_comision: comisionVentas + comisionCobros,
        updated_at: new Date(),
      },
    });
  }

  async getComisiones(empresa_id: string, supervisorId: string) {
    await this.findOne(empresa_id, supervisorId);
    return this.prisma.comisiones_supervisores.findMany({
      where: { supervisor_id: supervisorId, empresa_id },
      orderBy: { periodo_desde: 'desc' },
    });
  }

  async getReporteComisiones(empresa_id: string, opts: { periodo_desde?: string; periodo_hasta?: string; supervisor_id?: string }) {
    const where: any = { empresa_id };
    if (opts.supervisor_id) where.supervisor_id = opts.supervisor_id;
    if (opts.periodo_desde) where.periodo_desde = { gte: new Date(opts.periodo_desde) };
    if (opts.periodo_hasta) where.periodo_hasta = { lte: new Date(opts.periodo_hasta) };

    return this.prisma.comisiones_supervisores.findMany({
      where,
      include: {
        supervisor: { select: { nombre: true, apellido: true, codigo: true } },
      },
      orderBy: [{ supervisor_id: 'asc' }, { periodo_desde: 'desc' }],
    });
  }

  async getDetalleComision(empresa_id: string, comisionId: string) {
    const comision = await this.prisma.comisiones_supervisores.findFirst({
      where: { id: comisionId, empresa_id },
      include: {
        supervisor: {
          select: {
            id: true, nombre: true, apellido: true, codigo: true,
            porcentaje_comision_ventas: true, porcentaje_comision_cobros: true,
          },
        },
      },
    });
    if (!comision) throw new NotFoundException('Comisión no encontrada');

    const equipoRel = await this.prisma.supervisor_vendedores_cobradores.findMany({
      where: { supervisor_id: comision.supervisor_id },
      include: {
        vendedor_cobrador: {
          select: { id: true, nombre: true, apellido: true, tipo: true, codigo: true },
        },
      },
    });
    const equipoIds = equipoRel.map((e) => e.vendedor_cobrador_id);
    const desde = comision.periodo_desde;
    const hasta = new Date(comision.periodo_hasta);
    hasta.setHours(23, 59, 59, 999);

    const { ventasPorVc, cobrosPorVc } = await this.calcularTotalesEquipo(
      empresa_id,
      comision.supervisor_id,
      equipoIds,
      desde,
      hasta,
    );

    const equipoDetalle = equipoRel.map((e) => ({
      vendedor_cobrador: e.vendedor_cobrador,
      ventas: ventasPorVc.get(e.vendedor_cobrador_id) ?? 0,
      cobros: cobrosPorVc.get(e.vendedor_cobrador_id) ?? 0,
    }));

    return { comision, equipo: equipoDetalle };
  }

  async reporteComisionesResumenPdf(
    empresa_id: string,
    opts: { periodo_desde?: string; periodo_hasta?: string; supervisor_id?: string },
  ): Promise<Buffer> {
    const empresa = await this.getEmpresaHeader(empresa_id);
    const reporte = await this.getReporteComisiones(empresa_id, opts);

    const filas = reporte.map((r: any) => ({
      nombre: `${r.supervisor?.nombre ?? ''} ${r.supervisor?.apellido ?? ''}`.trim(),
      codigo: r.supervisor?.codigo ?? '',
      periodo: `${new Date(r.periodo_desde).toLocaleDateString('es-PY')} — ${new Date(r.periodo_hasta).toLocaleDateString('es-PY')}`,
      ventas: Number(r.total_ventas ?? 0),
      cobros: Number(r.total_cobros ?? 0),
      comision_ventas: Number(r.comision_ventas ?? 0),
      comision_cobros: Number(r.comision_cobros ?? 0),
      total_comision: Number(r.total_comision ?? 0),
      estado: r.estado ?? 'pendiente',
    }));

    const totales = filas.reduce(
      (acc, f) => ({
        ventas: acc.ventas + f.ventas,
        cobros: acc.cobros + f.cobros,
        comision_ventas: acc.comision_ventas + f.comision_ventas,
        comision_cobros: acc.comision_cobros + f.comision_cobros,
        total_comision: acc.total_comision + f.total_comision,
      }),
      { ventas: 0, cobros: 0, comision_ventas: 0, comision_cobros: 0, total_comision: 0 },
    );

    const desde = opts.periodo_desde ? new Date(opts.periodo_desde) : undefined;
    const hasta = opts.periodo_hasta ? new Date(opts.periodo_hasta) : undefined;

    return this.callMsvKude('comisiones-supervisores-resumen', {
      empresa,
      titulo: 'Resumen de Comisiones Supervisores',
      periodo: this.formatPeriodo(desde, hasta),
      filas,
      totales,
    });
  }

  async reporteComisionesDetallePdf(
    empresa_id: string,
    opts: { periodo_desde?: string; periodo_hasta?: string; supervisor_id?: string },
  ): Promise<Buffer> {
    const empresa = await this.getEmpresaHeader(empresa_id);
    const reporte = await this.getReporteComisiones(empresa_id, opts);

    const grupos: any[] = [];
    let totalVentas = 0;
    let totalCobros = 0;
    let totalComision = 0;

    for (const r of reporte as any[]) {
      const detalle = await this.getDetalleComision(empresa_id, r.id);
      const items = detalle.equipo.map((e) => ({
        nombre: `${e.vendedor_cobrador.nombre ?? ''} ${e.vendedor_cobrador.apellido ?? ''}`.trim(),
        codigo: e.vendedor_cobrador.codigo ?? '',
        tipo: e.vendedor_cobrador.tipo ?? '',
        ventas: e.ventas,
        cobros: e.cobros,
      }));

      grupos.push({
        nombre: `${r.supervisor?.nombre ?? ''} ${r.supervisor?.apellido ?? ''}`.trim(),
        codigo: r.supervisor?.codigo ?? '',
        periodo: `${new Date(r.periodo_desde).toLocaleDateString('es-PY')} — ${new Date(r.periodo_hasta).toLocaleDateString('es-PY')}`,
        pct_ventas: Number(r.supervisor?.porcentaje_comision_ventas ?? 0),
        pct_cobros: Number(r.supervisor?.porcentaje_comision_cobros ?? 0),
        estado: r.estado ?? 'pendiente',
        items,
        total_ventas: Number(r.total_ventas ?? 0),
        total_cobros: Number(r.total_cobros ?? 0),
        comision_ventas: Number(r.comision_ventas ?? 0),
        comision_cobros: Number(r.comision_cobros ?? 0),
        total_comision: Number(r.total_comision ?? 0),
      });

      totalVentas += Number(r.total_ventas ?? 0);
      totalCobros += Number(r.total_cobros ?? 0);
      totalComision += Number(r.total_comision ?? 0);
    }

    const desde = opts.periodo_desde ? new Date(opts.periodo_desde) : undefined;
    const hasta = opts.periodo_hasta ? new Date(opts.periodo_hasta) : undefined;

    return this.callMsvKude('comisiones-supervisores-detalle', {
      empresa,
      titulo: 'Detalle de Comisiones Supervisores',
      periodo: this.formatPeriodo(desde, hasta),
      grupos,
      totales: { ventas: totalVentas, cobros: totalCobros, comision: totalComision },
    });
  }

  async liquidarComision(empresa_id: string, comisionId: string) {
    const comision = await this.prisma.comisiones_supervisores.findFirst({
      where: { id: comisionId, empresa_id },
    });
    if (!comision) throw new NotFoundException('Comisión no encontrada');
    if (comision.estado === 'liquidada') throw new BadRequestException('La comisión ya está liquidada');

    const updated = await this.prisma.comisiones_supervisores.update({
      where: { id: comisionId },
      data: { estado: 'liquidada', updated_at: new Date() },
    });

    // Crear fila unificada en liquidaciones_comisiones para que aparezca en el panel de Liquidaciones.
    // Modelo actual de supervisor: la contabilización es one-shot (gasto + salida de caja) → estado='pagada'.
    const ahora = new Date();
    await this.prisma.liquidaciones_comisiones.create({
      data: {
        empresa_id,
        tipo: 'supervisor',
        supervisor_id: comision.supervisor_id,
        comision_supervisor_id: comision.id,
        periodo_desde: comision.periodo_desde,
        periodo_hasta: comision.periodo_hasta,
        total_comisiones: comision.total_comision,
        cantidad_facturas: 0,
        estado: 'pagada',
        fecha_pago: ahora,
        observaciones: comision.observaciones,
      },
    });

    try {
      await this.contabilidad.integrarComisionSupervisor(comisionId, empresa_id);
    } catch (e) {
      this.logger.error(`Error contabilizando comisión supervisor ${comisionId}: ${(e as Error).message}`);
    }

    return updated;
  }

  async eliminarComision(
    empresa_id: string,
    comisionId: string,
    userId?: string,
    meta?: { ip?: string; userAgent?: string },
  ) {
    const comision = await this.prisma.comisiones_supervisores.findFirst({
      where: { id: comisionId, empresa_id },
    });
    if (!comision) throw new NotFoundException('Comisión no encontrada');
    if (comision.estado === 'liquidada')
      throw new BadRequestException('No se puede eliminar una comisión liquidada. Anulá desde Liquidaciones.');

    await this.prisma.comisiones_supervisores.delete({ where: { id: comisionId } });

    await this.auditService.log({
      empresa_id,
      user_id: userId,
      action: 'DELETE',
      entity_type: 'comision_supervisor',
      entity_id: comisionId,
      descripcion: `Comisión de supervisor eliminada (período ${comision.periodo_desde.toISOString().slice(0, 10)} a ${comision.periodo_hasta.toISOString().slice(0, 10)})`,
      old_value: {
        supervisor_id: comision.supervisor_id,
        periodo_desde: comision.periodo_desde,
        periodo_hasta: comision.periodo_hasta,
        total_comision: comision.total_comision?.toString(),
        estado: comision.estado,
      },
      ip_address: meta?.ip,
      user_agent: meta?.userAgent,
    });

    return { ok: true };
  }
}
