import { BadGatewayException, BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { formatDateResponse } from 'src/utils/utilidades';
import { envs } from '../config/envs';
import { PrismaService } from '../prisma/prisma.service';
import { Decimal } from '@prisma/client/runtime/library';
import { ContabilidadIntegracionService } from '../contabilidad/services/integracion.service';

export interface ResumenComision {
  id: string;
  nombre: string;
  tipo: string;
  codigo: string | null;
  comisiones_pendientes: { cantidad: number; monto: number };
  comisiones_liquidadas: { cantidad: number; monto: number };
}

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

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

  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 fmtDate(d?: Date | string | null) {
    if (!d) return '';
    const dt = d instanceof Date ? d : new Date(d);
    return dt.toLocaleDateString('es-PY', { day: '2-digit', month: '2-digit', year: 'numeric' });
  }

  async getComisionesPendientes(empresaId: string, vendedorCobradorId?: string) {
    const whereClause: { empresa_id: string; estado: string; vendedor_cobrador_id?: string } = {
      empresa_id: empresaId,
      estado: 'pendiente',
    };
    if (vendedorCobradorId) whereClause.vendedor_cobrador_id = vendedorCobradorId;

    const comisiones = await this.prisma.comisiones.findMany({
      where: whereClause,
      include: {
        vendedor_cobrador: true,
        factura_cab: { include: { clientes: { include: { personas: true } }, factura_subtotales: true } },
      },
      orderBy: { created_at: 'desc' },
    });

    return comisiones.map((c) => ({
      id: c.id,
      tipo: c.tipo,
      monto_base: Number(c.monto_base),
      monto_comision: Number(c.monto_comision),
      estado: c.estado,
      created_at: c.created_at,
      vendedor_cobrador: {
        id: c.vendedor_cobrador.id,
        nombre: `${c.vendedor_cobrador.nombre} ${c.vendedor_cobrador.apellido || ''}`.trim(),
        tipo: c.vendedor_cobrador.tipo,
      },
      factura: c.factura_cab
        ? {
            id: c.factura_cab.id,
            numero: `${c.factura_cab.dest}-${c.factura_cab.dpunexp}-${c.factura_cab.dnumdoc}`,
            total: c.factura_cab.factura_subtotales?.[0]?.dtotope
              ? Number(c.factura_cab.factura_subtotales[0].dtotope)
              : 0,
            cliente: c.factura_cab.clientes?.personas?.razon_social,
          }
        : null,
    }));
  }

  async generarComisionVenta(empresaId: string, facturaId: string, vendedorCobradorId: string, montoBase: number) {
    const vendedor = await this.prisma.vendedores_cobradores.findFirst({
      where: { id: vendedorCobradorId, empresa_id: empresaId },
    });
    if (!vendedor) throw new NotFoundException('Vendedor no encontrado');

    const porcentaje = vendedor.comision_venta ? Number(vendedor.comision_venta) : 0;
    const montoFijo = vendedor.comision_fija ? Number(vendedor.comision_fija) : 0;
    let montoComision = porcentaje > 0 ? (montoBase * porcentaje) / 100 : 0;
    montoComision += montoFijo;

    if (montoComision <= 0) return null;

    return this.prisma.comisiones.create({
      data: {
        empresa_id: empresaId,
        vendedor_cobrador_id: vendedorCobradorId,
        factura_id: facturaId,
        tipo: 'venta',
        monto_base: new Decimal(montoBase),
        porcentaje_aplicado: porcentaje > 0 ? new Decimal(porcentaje) : null,
        monto_fijo_aplicado: montoFijo > 0 ? new Decimal(montoFijo) : null,
        monto_comision: new Decimal(montoComision),
        estado: 'pendiente',
      },
    });
  }

  async generarComisionCobranza(empresaId: string, facturaId: string, cobradorId: string, montoCobrado: number) {
    const cobrador = await this.prisma.vendedores_cobradores.findFirst({
      where: { id: cobradorId, empresa_id: empresaId },
    });
    if (!cobrador) throw new NotFoundException('Cobrador no encontrado');

    const porcentaje = cobrador.comision_cobranza ? Number(cobrador.comision_cobranza) : 0;
    const montoFijo = cobrador.comision_fija ? Number(cobrador.comision_fija) : 0;
    let montoComision = porcentaje > 0 ? (montoCobrado * porcentaje) / 100 : 0;
    montoComision += montoFijo;

    if (montoComision <= 0) return null;

    return this.prisma.comisiones.create({
      data: {
        empresa_id: empresaId,
        vendedor_cobrador_id: cobradorId,
        factura_id: facturaId,
        tipo: 'cobranza',
        monto_base: new Decimal(montoCobrado),
        porcentaje_aplicado: porcentaje > 0 ? new Decimal(porcentaje) : null,
        monto_fijo_aplicado: montoFijo > 0 ? new Decimal(montoFijo) : null,
        monto_comision: new Decimal(montoComision),
        estado: 'pendiente',
      },
    });
  }

  async crearLiquidacion(
    empresaId: string,
    vendedorCobradorId: string,
    periodoDesde: Date,
    periodoHasta: Date,
    observaciones?: string,
  ) {
    // Ajustar periodoHasta para incluir todo el día (23:59:59)
    const hastaFinDia = new Date(periodoHasta);
    hastaFinDia.setHours(23, 59, 59, 999);
    const comisiones = await this.prisma.comisiones.findMany({
      where: {
        empresa_id: empresaId,
        vendedor_cobrador_id: vendedorCobradorId,
        estado: 'pendiente',
        created_at: { gte: periodoDesde, lte: hastaFinDia },
      },
    });

    if (comisiones.length === 0) throw new BadRequestException('No hay comisiones pendientes en el período');

    const totalComisiones = comisiones.reduce((sum, c) => sum + Number(c.monto_comision), 0);

    const liquidacion = await this.prisma.liquidaciones_comisiones.create({
      data: {
        empresa_id: empresaId,
        tipo: 'vendedor',
        vendedor_cobrador_id: vendedorCobradorId,
        periodo_desde: periodoDesde,
        periodo_hasta: periodoHasta,
        total_comisiones: new Decimal(totalComisiones),
        cantidad_facturas: comisiones.length,
        estado: 'pendiente',
        observaciones,
      },
    });

    await this.prisma.comisiones.updateMany({
      where: { id: { in: comisiones.map((c) => c.id) } },
      data: { liquidacion_id: liquidacion.id, estado: 'liquidada', fecha_liquidacion: new Date() },
    });

    try {
      await this.contabilidad.integrarDevengamientoComisiones(liquidacion.id, empresaId);
    } catch (e) {
      this.logger.error(`Error contabilizando devengamiento ${liquidacion.id}: ${(e as Error).message}`);
    }

    return liquidacion;
  }

  async getLiquidaciones(empresaId: string, filters?: { vendedor_cobrador_id?: string; estado?: string; tipo?: string }) {
    const whereClause: any = { empresa_id: empresaId };
    if (filters?.vendedor_cobrador_id) whereClause.vendedor_cobrador_id = filters.vendedor_cobrador_id;
    if (filters?.estado) whereClause.estado = filters.estado;
    if (filters?.tipo) whereClause.tipo = filters.tipo;

    const liquidaciones = await this.prisma.liquidaciones_comisiones.findMany({
      where: whereClause,
      orderBy: { created_at: 'desc' },
      include: {
        supervisor: { select: { id: true, nombre: true, apellido: true, codigo: true } },
      },
    });

    const vendedorIds = [
      ...new Set(liquidaciones.map((l) => l.vendedor_cobrador_id).filter((id): id is string => !!id)),
    ];
    const vendedores = vendedorIds.length
      ? await this.prisma.vendedores_cobradores.findMany({ where: { id: { in: vendedorIds } } })
      : [];
    const vendedoresMap = new Map(vendedores.map((v) => [v.id, v]));

    return liquidaciones.map((l) => {
      const vendedor = l.vendedor_cobrador_id ? vendedoresMap.get(l.vendedor_cobrador_id) : undefined;
      const supervisor = l.supervisor;
      const beneficiario = supervisor
        ? {
            id: supervisor.id,
            nombre: `${supervisor.nombre} ${supervisor.apellido || ''}`.trim(),
            tipo: 'supervisor' as const,
            codigo: supervisor.codigo,
          }
        : vendedor
        ? {
            id: vendedor.id,
            nombre: `${vendedor.nombre} ${vendedor.apellido || ''}`.trim(),
            tipo: vendedor.tipo,
            codigo: vendedor.codigo,
          }
        : null;
      return {
        id: l.id,
        tipo: l.tipo,
        periodo_desde: formatDateResponse(l.periodo_desde),
        periodo_hasta: formatDateResponse(l.periodo_hasta),
        total_comisiones: Number(l.total_comisiones),
        cantidad_facturas: l.cantidad_facturas,
        estado: l.estado,
        fecha_pago: l.fecha_pago ? formatDateResponse(l.fecha_pago) : null,
        created_at: formatDateResponse(l.created_at),
        vendedor_cobrador: beneficiario,
      };
    });
  }

  async getLiquidacionDetalle(empresaId: string, liquidacionId: string) {
    const liquidacion = await this.prisma.liquidaciones_comisiones.findFirst({
      where: { id: liquidacionId, empresa_id: empresaId },
      include: {
        supervisor: { select: { id: true, nombre: true, apellido: true, codigo: true } },
        comision_supervisor: true,
      },
    });
    if (!liquidacion) throw new NotFoundException('Liquidación no encontrada');

    const esSupervisor = liquidacion.tipo === 'supervisor';

    const vendedor = !esSupervisor && liquidacion.vendedor_cobrador_id
      ? await this.prisma.vendedores_cobradores.findFirst({ where: { id: liquidacion.vendedor_cobrador_id } })
      : null;

    let comisionesPayload: any[] = [];
    if (esSupervisor && liquidacion.comision_supervisor) {
      const cs = liquidacion.comision_supervisor;
      comisionesPayload = [
        {
          id: cs.id,
          tipo: 'venta',
          monto_base: Number(cs.total_ventas),
          monto_comision: Number(cs.comision_ventas),
          created_at: formatDateResponse(cs.created_at),
          factura: null,
          concepto: 'Comisión sobre ventas del equipo',
        },
        {
          id: `${cs.id}-cobros`,
          tipo: 'cobranza',
          monto_base: Number(cs.total_cobros),
          monto_comision: Number(cs.comision_cobros),
          created_at: formatDateResponse(cs.created_at),
          factura: null,
          concepto: 'Comisión sobre cobros del equipo',
        },
      ].filter((row) => row.monto_base > 0 || row.monto_comision > 0);
    } else {
      const comisiones = await this.prisma.comisiones.findMany({
        where: { liquidacion_id: liquidacionId },
        include: { factura_cab: { include: { clientes: { include: { personas: true } }, factura_subtotales: true } } },
        orderBy: { created_at: 'asc' },
      });
      comisionesPayload = comisiones.map((c) => ({
        id: c.id,
        tipo: c.tipo,
        monto_base: Number(c.monto_base),
        monto_comision: Number(c.monto_comision),
        created_at: formatDateResponse(c.created_at),
        factura: c.factura_cab
          ? {
              id: c.factura_cab.id,
              numero: `${c.factura_cab.dest}-${c.factura_cab.dpunexp}-${c.factura_cab.dnumdoc}`,
              fecha: c.factura_cab.dfeemide,
              total: c.factura_cab.factura_subtotales?.[0]?.dtotope
                ? Number(c.factura_cab.factura_subtotales[0].dtotope)
                : 0,
              cliente: c.factura_cab.clientes?.personas?.razon_social,
            }
          : null,
      }));
    }

    const beneficiario = esSupervisor && liquidacion.supervisor
      ? {
          id: liquidacion.supervisor.id,
          nombre: `${liquidacion.supervisor.nombre} ${liquidacion.supervisor.apellido || ''}`.trim(),
          tipo: 'supervisor',
          codigo: liquidacion.supervisor.codigo,
        }
      : vendedor
      ? {
          id: vendedor.id,
          nombre: `${vendedor.nombre} ${vendedor.apellido || ''}`.trim(),
          tipo: vendedor.tipo,
          codigo: vendedor.codigo,
        }
      : null;

    return {
      id: liquidacion.id,
      tipo: liquidacion.tipo,
      periodo_desde: liquidacion.periodo_desde,
      periodo_hasta: formatDateResponse(liquidacion.periodo_hasta),
      total_comisiones: Number(liquidacion.total_comisiones),
      cantidad_facturas: liquidacion.cantidad_facturas,
      estado: liquidacion.estado,
      fecha_pago: formatDateResponse(liquidacion.fecha_pago),
      created_at: formatDateResponse(liquidacion.created_at),
      vendedor_cobrador: beneficiario,
      comisiones: comisionesPayload,
    };
  }

  async marcarComoPagada(empresaId: string, liquidacionId: string, fechaPago?: Date) {
    const liquidacion = await this.prisma.liquidaciones_comisiones.findFirst({
      where: { id: liquidacionId, empresa_id: empresaId },
    });
    if (!liquidacion) throw new NotFoundException('Liquidación no encontrada');
    if (liquidacion.tipo === 'supervisor')
      throw new BadRequestException('Las liquidaciones de supervisor se contabilizan al liquidar; no requieren pago manual.');
    if (liquidacion.estado === 'pagada') throw new BadRequestException('La liquidación ya está pagada');

    const updated = await this.prisma.liquidaciones_comisiones.update({
      where: { id: liquidacionId },
      data: { estado: 'pagada', fecha_pago: fechaPago || new Date() },
    });

    try {
      await this.contabilidad.integrarPagoComisiones(liquidacionId, empresaId, updated.fecha_pago ?? undefined);
    } catch (e) {
      this.logger.error(`Error contabilizando pago ${liquidacionId}: ${(e as Error).message}`);
    }

    return updated;
  }

  async anularLiquidacion(empresaId: string, liquidacionId: string) {
    const liquidacion = await this.prisma.liquidaciones_comisiones.findFirst({
      where: { id: liquidacionId, empresa_id: empresaId },
    });
    if (!liquidacion) throw new NotFoundException('Liquidación no encontrada');

    if (liquidacion.tipo === 'supervisor') {
      if (!liquidacion.comision_supervisor_id)
        throw new BadRequestException('Liquidación supervisor sin comisión vinculada');

      await this.prisma.comisiones_supervisores.update({
        where: { id: liquidacion.comision_supervisor_id },
        data: { estado: 'pendiente', updated_at: new Date() },
      });

      const updated = await this.prisma.liquidaciones_comisiones.update({
        where: { id: liquidacionId },
        data: { estado: 'anulada' },
      });

      try {
        await this.contabilidad.revertirDocumento('comision_supervisor', liquidacion.comision_supervisor_id, null);
      } catch (e) {
        this.logger.error(`Error revirtiendo asiento comisión supervisor ${liquidacion.comision_supervisor_id}: ${(e as Error).message}`);
      }

      return updated;
    }

    if (liquidacion.estado === 'pagada') throw new BadRequestException('No se puede anular una liquidación pagada');

    await this.prisma.comisiones.updateMany({
      where: { liquidacion_id: liquidacionId },
      data: { liquidacion_id: null, estado: 'pendiente', fecha_liquidacion: null },
    });

    const updated = await this.prisma.liquidaciones_comisiones.update({
      where: { id: liquidacionId },
      data: { estado: 'anulada' },
    });

    try {
      await this.contabilidad.revertirDocumento('liquidacion_comisiones_pago', liquidacionId, null);
      await this.contabilidad.revertirDocumento('liquidacion_comisiones_deveng', liquidacionId, null);
    } catch (e) {
      this.logger.error(`Error revirtiendo asientos de liquidación ${liquidacionId}: ${(e as Error).message}`);
    }

    return updated;
  }

  async liquidacionComprobantePdf(empresaId: string, liquidacionId: string): Promise<Buffer> {
    const empresa = await this.getEmpresaHeader(empresaId);
    const detalle = await this.getLiquidacionDetalle(empresaId, liquidacionId);
    return this.callMsvKude('liquidacion-comprobante', {
      empresa,
      titulo: 'Comprobante de Liquidación',
      periodo: `${this.fmtDate(detalle.periodo_desde)} — ${this.fmtDate(detalle.periodo_hasta)}`,
      liquidacion: detalle,
    });
  }

  async liquidacionesListaPdf(
    empresaId: string,
    filters: { vendedor_cobrador_id?: string; estado?: string; moneda_id?: string },
  ): Promise<Buffer> {
    const empresa = await this.getEmpresaHeader(empresaId);
    const lista = await this.getLiquidaciones(empresaId, {
      vendedor_cobrador_id: filters.vendedor_cobrador_id,
      estado: filters.estado,
    });

    let moneda: string | undefined;
    if (filters.moneda_id) {
      const m = await this.prisma.moneda.findUnique({ where: { id: filters.moneda_id } });
      if (m) moneda = `${m.codigo || ''} - ${m.descripcion || ''}`.trim();
    }

    const filas = lista.map((l) => ({
      nombre: l.vendedor_cobrador?.nombre ?? '-',
      tipo: l.vendedor_cobrador?.tipo ?? '',
      periodo_desde: l.periodo_desde,
      periodo_hasta: l.periodo_hasta,
      cantidad_facturas: l.cantidad_facturas,
      total_comisiones: l.total_comisiones,
      estado: l.estado,
      fecha_pago: l.fecha_pago,
    }));

    const totales = filas.reduce(
      (acc, f) => ({
        cantidad_facturas: acc.cantidad_facturas + Number(f.cantidad_facturas || 0),
        total_comisiones: acc.total_comisiones + Number(f.total_comisiones || 0),
      }),
      { cantidad_facturas: 0, total_comisiones: 0 },
    );

    return this.callMsvKude('liquidaciones-lista', {
      empresa,
      titulo: 'Historial de Liquidaciones',
      moneda,
      filas,
      totales,
    });
  }

  async getResumenComisiones(
    empresaId: string,
    monedaId?: string,
  ): Promise<(ResumenComision & { moneda?: { id: string; codigo: string; simbolo: string } })[]> {
    const vendedores = await this.prisma.vendedores_cobradores.findMany({
      where: { empresa_id: empresaId, active: true },
    });

    let monedaInfo: { id: string; codigo: string; simbolo: string } | null = null;
    if (monedaId) {
      const moneda = await this.prisma.moneda.findUnique({ where: { id: monedaId } });
      if (moneda) {
        monedaInfo = {
          id: moneda.id,
          codigo: moneda.codigo || 'PYG',
          simbolo: moneda.simbolo || 'Gs.',
        };
      }
    }

    const resumen: (ResumenComision & { moneda?: { id: string; codigo: string; simbolo: string } })[] = [];

    for (const vendedor of vendedores) {
      const whereBase: { vendedor_cobrador_id: string; factura_cab?: { moneda_id: string } } = {
        vendedor_cobrador_id: vendedor.id,
      };
      if (monedaId) {
        whereBase.factura_cab = { moneda_id: monedaId };
      }

      const pendientes = await this.prisma.comisiones.aggregate({
        where: { ...whereBase, estado: 'pendiente' },
        _sum: { monto_comision: true },
        _count: true,
      });

      const liquidadas = await this.prisma.comisiones.aggregate({
        where: { ...whereBase, estado: 'liquidada' },
        _sum: { monto_comision: true },
        _count: true,
      });

      resumen.push({
        id: vendedor.id,
        nombre: (vendedor.nombre + ' ' + (vendedor.apellido || '')).trim(),
        tipo: vendedor.tipo,
        codigo: vendedor.codigo,
        comisiones_pendientes: {
          cantidad: pendientes._count,
          monto: pendientes._sum.monto_comision ? Number(pendientes._sum.monto_comision) : 0,
        },
        comisiones_liquidadas: {
          cantidad: liquidadas._count,
          monto: liquidadas._sum.monto_comision ? Number(liquidadas._sum.monto_comision) : 0,
        },
        moneda: monedaInfo || { id: '', codigo: 'PYG', simbolo: 'Gs.' },
      });
    }

    return resumen.filter((r) => r.comisiones_pendientes.cantidad > 0 || r.comisiones_liquidadas.cantidad > 0);
  }
}
