import { sincronizarCuentaCobrar, type TxSincronizacion } from 'src/common/utils/sincronizar-cuenta.util';
import {
  BadRequestException,
  ConflictException,
  HttpException,
  HttpStatus,
  Injectable,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import {
  CobMoraEstado,
  CobRefinanciacionEstado,
  Prisma,
} from '@prisma/client';
import { AuditService } from '../audit/audit.service';
import { envs } from '../config/envs';
import { PrismaService } from '../prisma/prisma.service';
import { GestionMoraService } from './gestion-mora.service';
import {
  calcularProximoVencimiento,
  PoliticaCobro,
} from '../cobros/politica-cobro.util';
import {
  AnularRefinanciacionDto,
  CreateRefinanciacionDto,
  EjecutarRefinanciacionDto,
  PoliticaInteres,
  QueryRefinanciacionesDto,
  SimularRefinanciacionDto,
} from './dto/refinanciacion.dto';

interface CuotaPropuesta {
  nro_cuota: number;
  fecha_vencimiento: string;
  monto_cuota: number;
}

interface SimulacionResult {
  cliente_id: string;
  cuotas_originales: Array<{
    factura_cuota_id: string;
    factura_cab_id: string;
    nro_cuota: number | null;
    fecha_venc: string;
    saldo_capital: number;
    saldo_interes: number;
  }>;
  monto_capital_orig: number;
  monto_interes_orig: number;
  monto_interes_exon: number;
  monto_capital_nuevo: number;
  cantidad_cuotas: number;
  monto_cuota: number;
  tasa_interes: number;
  politica_interes: PoliticaInteres;
  cuotas_propuestas: CuotaPropuesta[];
}

@Injectable()
export class RefinanciacionesService {
  private readonly logger = new Logger(RefinanciacionesService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly auditService: AuditService,
    private readonly gestionMora: GestionMoraService,
  ) {}

  // ───────────────────────────────────────────────────────────────────────────
  // Simulación
  // ───────────────────────────────────────────────────────────────────────────

  async simular(dto: SimularRefinanciacionDto, empresa_id: string): Promise<SimulacionResult> {
    const cuotas = await this.prisma.factura_cuotas.findMany({
      where: {
        id: { in: dto.factura_cuota_ids },
        factura_cab: { empresa_id, cliente_id: dto.cliente_id },
      },
      include: {
        factura_cab: { select: { id: true, cliente_id: true, empresa_id: true } },
      },
    });

    if (cuotas.length !== dto.factura_cuota_ids.length) {
      throw new BadRequestException(
        'Algunas cuotas no existen o no pertenecen al cliente',
      );
    }

    const yaRefinanciadas = cuotas.filter((c) => c.estado === 'REFINANCIADA');
    if (yaRefinanciadas.length > 0) {
      throw new BadRequestException(
        `${yaRefinanciadas.length} cuota(s) ya fueron refinanciadas previamente`,
      );
    }

    const capitalOrig = cuotas.reduce(
      (acc, c) => acc.add(c.saldo_pendiente ?? new Prisma.Decimal(0)),
      new Prisma.Decimal(0),
    );

    const interesOrig = new Prisma.Decimal(0);

    let capitalNuevo = capitalOrig;
    let interesExon = new Prisma.Decimal(0);
    if (dto.politica_interes === 'CAPITALIZAR') {
      capitalNuevo = capitalOrig.add(interesOrig);
    } else if (dto.politica_interes === 'EXONERAR') {
      interesExon = interesOrig;
    }

    const cuotasPropuestas = this.calcularCuotasPropuestas(
      capitalNuevo.toNumber(),
      dto.tasa_interes,
      dto.cantidad_cuotas,
      new Date(dto.fecha_primera_cuota),
      dto.intervalo_dias ?? 30,
    );
    const montoCuota = cuotasPropuestas[0]?.monto_cuota ?? 0;

    return {
      cliente_id: dto.cliente_id,
      cuotas_originales: cuotas.map((c) => ({
        factura_cuota_id: c.id,
        factura_cab_id: c.factura_cab_id,
        nro_cuota: c.nro_cuota,
        fecha_venc: c.dvenccuo.toISOString().slice(0, 10),
        saldo_capital: Number(c.saldo_pendiente ?? 0),
        saldo_interes: 0,
      })),
      monto_capital_orig: capitalOrig.toNumber(),
      monto_interes_orig: interesOrig.toNumber(),
      monto_interes_exon: interesExon.toNumber(),
      monto_capital_nuevo: capitalNuevo.toNumber(),
      cantidad_cuotas: dto.cantidad_cuotas,
      monto_cuota: montoCuota,
      tasa_interes: dto.tasa_interes,
      politica_interes: dto.politica_interes,
      cuotas_propuestas: cuotasPropuestas,
    };
  }

  /**
   * Cálculo simple lineal con interés nominal anual sobre saldo proporcional al plazo.
   *
   * Si `politica` viene con `dia_fijo_pago_1/2` o `dia_cobro_semana`, respeta esos días.
   * Sin política, cae al comportamiento clásico: `fecha_primera + i * intervalo_dias`.
   */
  private calcularCuotasPropuestas(
    capital: number,
    tasaAnual: number,
    n: number,
    fechaPrimera: Date,
    intervaloDias: number,
    politica: PoliticaCobro | null = null,
  ): CuotaPropuesta[] {
    const factor = 1 + (tasaAnual / 100) * ((n * intervaloDias) / 365);
    const total = capital * factor;
    const cuota = Math.round((total / n) * 100) / 100;
    const out: CuotaPropuesta[] = [];

    const politicaCombinada: PoliticaCobro = {
      intervalo_dias: intervaloDias,
      dia_fijo_pago_1: politica?.dia_fijo_pago_1 ?? null,
      dia_fijo_pago_2: politica?.dia_fijo_pago_2 ?? null,
      dia_cobro_semana: politica?.dia_cobro_semana ?? null,
    };

    let fechaAnterior = new Date(fechaPrimera);
    for (let i = 0; i < n; i++) {
      const d =
        i === 0
          ? new Date(fechaPrimera)
          : calcularProximoVencimiento(
              politicaCombinada,
              new Date(fechaAnterior.getTime() + 24 * 60 * 60 * 1000),
              i,
            );
      out.push({
        nro_cuota: i + 1,
        fecha_vencimiento: d.toISOString().slice(0, 10),
        monto_cuota: cuota,
      });
      fechaAnterior = d;
    }
    return out;
  }

  // ───────────────────────────────────────────────────────────────────────────
  // Crear borrador
  // ───────────────────────────────────────────────────────────────────────────

  async create(dto: CreateRefinanciacionDto, empresa_id: string, user_id: string) {
    const sim = await this.simular(dto, empresa_id);

    const refi = await this.prisma.$transaction(async (tx) => {
      const r = await tx.cob_refinanciacion.create({
        data: {
          empresa_id,
          cliente_id: dto.cliente_id,
          motivo: dto.motivo,
          monto_capital_orig: new Prisma.Decimal(sim.monto_capital_orig),
          monto_interes_orig: new Prisma.Decimal(sim.monto_interes_orig),
          monto_interes_exon: new Prisma.Decimal(sim.monto_interes_exon),
          monto_capital_nuevo: new Prisma.Decimal(sim.monto_capital_nuevo),
          cantidad_cuotas: sim.cantidad_cuotas,
          monto_cuota: new Prisma.Decimal(sim.monto_cuota),
          tasa_interes: new Prisma.Decimal(sim.tasa_interes),
          fecha_primera_cuota: new Date(dto.fecha_primera_cuota),
          politica_interes: dto.politica_interes,
          documento_url: dto.documento_url ?? null,
          usuario_id: user_id,
          estado: CobRefinanciacionEstado.BORRADOR,
        },
      });

      await tx.cob_refinanciacion_cuota.createMany({
        data: sim.cuotas_originales.map((c) => ({
          refinanciacion_id: r.id,
          factura_cuota_id: c.factura_cuota_id,
          saldo_capital: new Prisma.Decimal(c.saldo_capital),
          saldo_interes: new Prisma.Decimal(c.saldo_interes),
        })),
      });

      return r;
    });

    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'CREATE',
      entity_type: 'cob_refinanciacion',
      entity_id: refi.id,
      descripcion:
        `Refinanciación borrador | Cliente ${dto.cliente_id} | Cuotas orig: ${dto.factura_cuota_ids.length}` +
        ` | Política: ${dto.politica_interes} | Capital nuevo: ${sim.monto_capital_nuevo}`,
      new_value: {
        cuotas_originales: dto.factura_cuota_ids,
        cantidad_cuotas: sim.cantidad_cuotas,
        monto_capital_nuevo: sim.monto_capital_nuevo,
        tasa_interes: sim.tasa_interes,
      },
    });

    return refi;
  }

  // ───────────────────────────────────────────────────────────────────────────
  // Ejecutar
  // ───────────────────────────────────────────────────────────────────────────

  async ejecutar(
    id: string,
    dto: EjecutarRefinanciacionDto,
    empresa_id: string,
    user_id: string,
  ) {
    const refi = await this.prisma.cob_refinanciacion.findFirst({
      where: { id, empresa_id },
      include: { cuotas_originales: true },
    });
    if (!refi) throw new NotFoundException('Refinanciación no encontrada');
    if (refi.estado !== CobRefinanciacionEstado.BORRADOR &&
        refi.estado !== CobRefinanciacionEstado.ACEPTADA) {
      throw new ConflictException(
        `No se puede ejecutar en estado ${refi.estado}`,
      );
    }

    const cuotaIds = refi.cuotas_originales.map((c) => c.factura_cuota_id);
    const cuotas = await this.prisma.factura_cuotas.findMany({
      where: { id: { in: cuotaIds } },
      select: { id: true, factura_cab_id: true, estado: true, saldo_pendiente: true },
    });
    const yaRefinanc = cuotas.filter((c) => c.estado === 'REFINANCIADA');
    if (yaRefinanc.length > 0) {
      throw new ConflictException(
        'Hay cuotas ya marcadas como REFINANCIADA, no se puede re-ejecutar',
      );
    }

    const facturaIds = Array.from(new Set(cuotas.map((c) => c.factura_cab_id)));

    const result = await this.prisma.$transaction(async (tx) => {
      // Marcar cuotas originales como REFINANCIADA y saldo 0
      await tx.factura_cuotas.updateMany({
        where: { id: { in: cuotaIds } },
        data: { estado: 'REFINANCIADA', saldo_pendiente: new Prisma.Decimal(0) },
      });

      // Recalcular saldo de cada factura afectada
      for (const facturaId of facturaIds) {
        const agg = await tx.factura_cuotas.aggregate({
          where: { factura_cab_id: facturaId },
          _sum: { saldo_pendiente: true },
        });
        const nuevoSaldo = agg._sum.saldo_pendiente ?? new Prisma.Decimal(0);
        await tx.factura_cab.update({
          where: { id: facturaId },
          data: { saldo_pendiente: nuevoSaldo },
        });
        // La factura se recalculaba pero la cuenta y el cliente no: seguían
        // reclamando la deuda vieja además de la refinanciada, contándola dos veces.
        await sincronizarCuentaCobrar(tx as unknown as TxSincronizacion, facturaId);
      }

      // Crear solicitud_credito nueva con tipo_origen REFINANCIACION
      const cronograma = this.calcularCuotasPropuestas(
        Number(refi.monto_capital_nuevo),
        Number(refi.tasa_interes),
        refi.cantidad_cuotas,
        refi.fecha_primera_cuota,
        30,
      );

      const solicitud = await tx.solicitud_credito.create({
        data: {
          empresa_id,
          cliente_id: refi.cliente_id,
          estado: 'aprobada',
          fecha_solicitud: new Date(),
          fecha_aprobacion: new Date(),
          aprobado_por: dto.autorizado_por_id ?? user_id,
          moneda: 'PYG',
          monto_total: refi.monto_capital_nuevo,
          cant_cuotas_total: refi.cantidad_cuotas,
          cuota_inicial_total: new Prisma.Decimal(0),
          saldo_financiar: refi.monto_capital_nuevo,
          fecha_primera_cuota: refi.fecha_primera_cuota,
          cronograma: cronograma as unknown as Prisma.InputJsonValue,
          observaciones:
            `Origen: refinanciación ${refi.id} | Política: ${refi.politica_interes} | Cuotas reemplazadas: ${cuotaIds.length}`,
          creado_por: user_id,
          tipo_origen: 'REFINANCIACION',
        },
      });

      const refiActualizado = await tx.cob_refinanciacion.update({
        where: { id },
        data: {
          estado: CobRefinanciacionEstado.EJECUTADA,
          solicitud_credito_nueva_id: solicitud.id,
          autorizado_por_id: dto.autorizado_por_id ?? null,
          documento_url: dto.documento_url ?? refi.documento_url,
          fecha_ejecucion: new Date(),
        },
      });

      return { refi: refiActualizado, solicitud_id: solicitud.id };
    });

    // Hook: transicionar gestión de mora activa a REFINANCIADA (fire-and-forget no-throw)
    try {
      const gestionActiva = await this.prisma.cob_gestion_mora.findFirst({
        where: {
          empresa_id,
          cliente_id: refi.cliente_id,
          deleted_at: null,
          estado_actual: {
            in: [
              CobMoraEstado.GESTION_INTERNA,
              CobMoraEstado.INFORMCONF,
              CobMoraEstado.DEMANDA,
              CobMoraEstado.AL_DIA,
            ],
          },
        },
        orderBy: { fecha_ingreso: 'desc' },
      });
      if (gestionActiva) {
        await this.gestionMora.transicionar(
          gestionActiva.id,
          {
            estado_nuevo: CobMoraEstado.REFINANCIADA,
            motivo: `Refinanciación ${refi.id} ejecutada`,
            documento_url: dto.documento_url ?? refi.documento_url ?? undefined,
            autorizado_por_id: dto.autorizado_por_id ?? undefined,
          } as any,
          empresa_id,
          user_id,
        );
      }
    } catch (err) {
      this.logger.warn(
        `Refinanciación ${refi.id} ejecutada, pero falló la transición de gestión de mora: ${
          err instanceof Error ? err.message : String(err)
        }`,
      );
    }

    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'EXECUTE',
      entity_type: 'cob_refinanciacion',
      entity_id: refi.id,
      descripcion:
        `Refinanciación ejecutada | Cliente ${refi.cliente_id} | Cuotas reemplazadas: ${cuotaIds.length}` +
        ` | Solicitud crédito nueva: ${result.solicitud_id}`,
      old_value: { estado: refi.estado },
      new_value: {
        estado: CobRefinanciacionEstado.EJECUTADA,
        solicitud_credito_nueva_id: result.solicitud_id,
        cuotas_marcadas: cuotaIds,
      },
    });

    return result.refi;
  }

  // ───────────────────────────────────────────────────────────────────────────
  // Anular
  // ───────────────────────────────────────────────────────────────────────────

  async anular(
    id: string,
    dto: AnularRefinanciacionDto,
    empresa_id: string,
    user_id: string,
  ) {
    const refi = await this.prisma.cob_refinanciacion.findFirst({
      where: { id, empresa_id },
    });
    if (!refi) throw new NotFoundException('Refinanciación no encontrada');
    if (refi.estado === CobRefinanciacionEstado.ANULADA) {
      throw new ConflictException('Ya está anulada');
    }
    if (refi.estado === CobRefinanciacionEstado.EJECUTADA) {
      throw new ConflictException(
        'No se puede anular una refinanciación ya ejecutada — debe crearse una contra-operación manual',
      );
    }

    const actualizado = await this.prisma.cob_refinanciacion.update({
      where: { id },
      data: {
        estado: CobRefinanciacionEstado.ANULADA,
        fecha_anulacion: new Date(),
        motivo_anulacion: dto.motivo_anulacion,
      },
    });

    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'ANULAR',
      entity_type: 'cob_refinanciacion',
      entity_id: refi.id,
      descripcion: `Refinanciación anulada | Motivo: ${dto.motivo_anulacion}`,
      old_value: { estado: refi.estado },
      new_value: { estado: CobRefinanciacionEstado.ANULADA },
    });

    return actualizado;
  }

  // ───────────────────────────────────────────────────────────────────────────
  // Lectura
  // ───────────────────────────────────────────────────────────────────────────

  async findAll(empresa_id: string, query: QueryRefinanciacionesDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 20;
    const where: Prisma.cob_refinanciacionWhereInput = { empresa_id };
    if (query.cliente_id) where.cliente_id = query.cliente_id;
    if (query.estado) where.estado = query.estado;

    const [data, total] = await this.prisma.$transaction([
      this.prisma.cob_refinanciacion.findMany({
        where,
        include: {
          cliente: { select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true } } } },
        },
        orderBy: { fecha: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.cob_refinanciacion.count({ where }),
    ]);

    return { data, total, page, limit };
  }

  async findOne(id: string, empresa_id: string) {
    const refi = await this.prisma.cob_refinanciacion.findFirst({
      where: { id, empresa_id },
      include: {
        cliente: {
          select: {
            id: true,
            nombre_fantasia: true,
            personas: { select: { razon_social: true, ruc: true } },
          },
        },
        cuotas_originales: {
          include: {
            factura_cuota: {
              select: {
                id: true,
                nro_cuota: true,
                dvenccuo: true,
                dmoncuota: true,
                estado: true,
                factura_cab: { select: { id: true, dest: true, dpunexp: true, dnumdoc: true } },
              },
            },
          },
        },
      },
    });
    if (!refi) throw new NotFoundException('Refinanciación no encontrada');
    return refi;
  }

  // ───────────────────────────────────────────────────────────────────────────
  // PDF
  // ───────────────────────────────────────────────────────────────────────────

  async generatePdf(
    id: string,
    empresa_id: string,
    options: { tipo_retorno: 'view' | 'base64' },
  ): Promise<{ pdfBuffer?: Buffer; base64?: string }> {
    const refi = await this.findOne(id, empresa_id);
    const empresa = await this.prisma.empresas.findUnique({
      where: { id: empresa_id },
      select: {
        razon_social: true,
        nombre_fantasia: true,
        ruc: true,
        dv: true,
        celular: true,
        email: true,
      },
    });

    const cronograma = this.calcularCuotasPropuestas(
      Number(refi.monto_capital_nuevo),
      Number(refi.tasa_interes),
      refi.cantidad_cuotas,
      refi.fecha_primera_cuota,
      30,
    );

    const payload = {
      tipo: options.tipo_retorno,
      empresa: empresa
        ? {
            razon_social: empresa.razon_social,
            // Nombre comercial arriba y razón social debajo en el PDF (criterio de la factura).
            nombre_fantasia: empresa.nombre_fantasia,
            ruc: empresa.ruc,
            dv: empresa.dv,
            celular: empresa.celular,
            email: empresa.email,
          }
        : {},
      cliente: {
        id: refi.cliente?.id,
        razon_social:
          refi.cliente?.nombre_fantasia ||
          refi.cliente?.personas?.razon_social ||
          'Cliente',
        ruc: refi.cliente?.personas?.ruc ?? null,
      },
      refinanciacion: {
        id: refi.id,
        fecha: refi.fecha,
        motivo: refi.motivo,
        estado: refi.estado,
        politica_interes: refi.politica_interes,
        monto_capital_orig: Number(refi.monto_capital_orig),
        monto_interes_orig: Number(refi.monto_interes_orig),
        monto_interes_exon: Number(refi.monto_interes_exon),
        monto_capital_nuevo: Number(refi.monto_capital_nuevo),
        cantidad_cuotas: refi.cantidad_cuotas,
        monto_cuota: Number(refi.monto_cuota),
        tasa_interes: Number(refi.tasa_interes),
        fecha_primera_cuota: refi.fecha_primera_cuota,
        documento_url: refi.documento_url,
        solicitud_credito_nueva_id: refi.solicitud_credito_nueva_id,
      },
      cuotas_originales: refi.cuotas_originales.map((c) => {
        const f = c.factura_cuota?.factura_cab;
        const factura_numero = f ? `${f.dest}-${f.dpunexp}-${f.dnumdoc}` : null;
        return {
          nro_cuota: c.factura_cuota?.nro_cuota,
          factura_numero,
          fecha_venc: c.factura_cuota?.dvenccuo,
          monto_cuota_orig: Number(c.factura_cuota?.dmoncuota ?? 0),
          saldo_capital: Number(c.saldo_capital),
          saldo_interes: Number(c.saldo_interes),
        };
      }),
      cuotas_nuevas: cronograma,
      fecha_generacion: new Date(),
    };

    try {
      const url = `${envs.apiGeneradorPDF}/api/refinanciacion/generate-pdf`;
      const resp = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
      if (!resp.ok) {
        const txt = await resp.text();
        throw new HttpException(
          `msv-kude error: ${resp.status} ${txt}`,
          HttpStatus.BAD_GATEWAY,
        );
      }

      if (options.tipo_retorno === 'base64') {
        const json = (await resp.json()) as { pdf_base64?: string };
        return { base64: json.pdf_base64 };
      }
      const arrayBuffer = await resp.arrayBuffer();
      return { pdfBuffer: Buffer.from(arrayBuffer) };
    } catch (err) {
      if (err instanceof HttpException) throw err;
      throw new HttpException(
        `Error generando PDF de refinanciación: ${
          err instanceof Error ? err.message : String(err)
        }`,
        HttpStatus.BAD_GATEWAY,
      );
    }
  }
}
