import { Injectable, Logger } from '@nestjs/common';
import { AlertasEmpresaService } from 'src/alertas-empresa/alertas-empresa.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { datosClientes, hoyPy, isoDia, sumarDias, clienteDe } from '../common/inm-utils';
import { ComisionesInmService } from '../comisiones/comisiones.service';
import { CotizacionesInmService } from '../cotizaciones/cotizaciones.service';
import { InusualesInmService } from '../cumplimiento/inusuales.service';
import { PostventaInmService } from '../postventa/postventa.service';
import { OperacionesInmService } from '../operaciones/operaciones.service';

const VIVAS_CON_SALDO = ['RESERVADA', 'BOLETO_FIRMADO', 'ESCRITURADA', 'ENTREGADA'] as const;

/**
 * Cartera inmobiliaria (M116): la deuda pendiente vive en el cronograma del vertical
 * (decisión 1), así que la mora, la cartera por proyecto y los avisos los lleva este
 * módulo y no Cobranzas, que sólo ve cuotas de facturas.
 */
@Injectable()
export class CarteraInmService {
  private readonly logger = new Logger(CarteraInmService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly operaciones: OperacionesInmService,
    private readonly cotizaciones: CotizacionesInmService,
    private readonly alertas: AlertasEmpresaService,
    private readonly comisiones: ComisionesInmService,
    private readonly postventa: PostventaInmService,
    private readonly inusuales: InusualesInmService,
  ) {}

  /** Operaciones con saldo, su estado de cuenta resumido y totales por moneda. */
  async cartera(empresa_id: string, f: { proyecto_id?: string; solo_vencidas?: boolean }) {
    const params = await this.prisma.inm_parametros.findUnique({
      where: { empresa_id },
      select: { tasa_mora_mensual: true },
    });
    const ops = await this.prisma.inm_operaciones.findMany({
      where: { empresa_id, estado: { in: [...VIVAS_CON_SALDO] }, ...(f.proyecto_id && { proyecto_id: f.proyecto_id }) },
      include: {
        moneda: { select: { codigo: true } },
        inm_unidades: { select: { id: true, codigo: true, tipo: true } },
        inm_proyectos: { select: { id: true, codigo: true, nombre: true } },
        inm_operacion_cuotas: { include: { inm_hitos: { select: { id: true, fecha_cumplido: true } } } },
      },
      orderBy: { numero: 'asc' },
    });
    const clientes = await datosClientes(
      this.prisma,
      empresa_id,
      ops.map((o) => o.cliente_id),
    );
    const filas = ops
      .map((o) => {
        const ec = this.operaciones.estadoCuenta(o, Number(params?.tasa_mora_mensual ?? 0));
        return {
          id: o.id,
          numero: o.numero,
          estado: o.estado,
          moneda: o.moneda?.codigo ?? 'PYG',
          proyecto: o.inm_proyectos,
          unidad: o.inm_unidades,
          cliente: clienteDe(clientes, o.cliente_id),
          ...ec.resumen,
        };
      })
      .filter((x) => x.saldo > 0 && (!f.solo_vencidas || x.vencido > 0));

    const totales: Record<
      string,
      { saldo: number; vencido: number; mora_estimada: number; operaciones: number; con_atraso: number }
    > = {};
    for (const x of filas) {
      const t = (totales[x.moneda] ??= { saldo: 0, vencido: 0, mora_estimada: 0, operaciones: 0, con_atraso: 0 });
      t.saldo += x.saldo;
      t.vencido += x.vencido;
      t.mora_estimada += x.mora_estimada;
      t.operaciones += 1;
      if (x.vencido > 0) t.con_atraso += 1;
    }
    return { filas, totales };
  }

  /**
   * Tareas diarias, idempotentes (las alertas se deduplican por clave):
   * 1. Cotizaciones vencidas.
   * 2. Reservas: vencidas sin seña cobrada → se liberan solas; con seña → alerta; por
   *    vencer en 48 h → alerta al vendedor.
   * 3. Alerta de rescisión por cuotas vencidas (umbral de los parámetros).
   * 4. Lotes con el porcentaje pagado que habilita a exigir escritura (25 % por defecto).
   */
  async tareasDiarias(hoy = hoyPy()) {
    const r = { cotizaciones_vencidas: 0, reservas_liberadas: 0, alertas: 0, comisiones: 0, inusuales: 0 };
    r.cotizaciones_vencidas = await this.cotizaciones.vencerCotizaciones(hoy);

    const empresas = await this.prisma.inm_parametros.findMany();
    for (const p of empresas) {
      try {
        r.alertas += await this.reservas(p.empresa_id, hoy, r);
        r.alertas += await this.atrasosYEscritura(
          p.empresa_id,
          Number(p.cuotas_alerta_rescision),
          Number(p.pct_aviso_escritura),
          Number(p.tasa_mora_mensual),
        );
        r.alertas += await this.garantias(p.empresa_id);
        r.comisiones += await this.devengoDeRespaldo(p.empresa_id);
        r.inusuales += await this.inusuales.detectarFraccionamientos(p.empresa_id, hoy);
      } catch (e) {
        this.logger.error(`Tareas diarias inmobiliario empresa=${p.empresa_id}: ${(e as Error).message}`);
      }
    }
    return r;
  }

  /** Por si un evento no devengó (p. ej. se cayó el proceso en el medio). Idempotente. */
  private async devengoDeRespaldo(empresa_id: string) {
    const ops = await this.prisma.inm_operaciones.findMany({
      where: { empresa_id, estado: { notIn: ['DESISTIDA', 'RESCINDIDA'] } },
      select: { id: true },
    });
    let n = 0;
    for (const op of ops) n += await this.comisiones.devengar(op.id, empresa_id);
    return n;
  }

  private async garantias(empresa_id: string) {
    const entregas = await this.postventa.garantiasPorVencer(empresa_id, 30);
    for (const e of entregas) {
      await this.alertas.registrar({
        empresaId: empresa_id,
        clave: `inm:garantia:${e.operacion_id}`,
        tipo: 'inm_garantia_vence',
        criticidad: 'baja',
        titulo: `Garantía por vencer: ${e.inm_operaciones.inm_unidades.codigo} (${e.inm_operaciones.numero})`,
        descripcion: `La garantía de la unidad vence el ${isoDia(e.garantia_hasta)}. Revisá que no queden observaciones de la entrega sin resolver.`,
        accionSugerida: 'Abrir la operación',
        modulo: 'INMOBILIARIO',
        privilegioRequerido: 'INM_OPE_OPERACION_VER',
        ruta: `/inmobiliario/operaciones/${e.operacion_id}`,
        entidadTipo: 'inm_operaciones',
        entidadId: e.operacion_id,
        origen: 'regla',
      });
    }
    return entregas.length;
  }

  private async reservas(empresa_id: string, hoy: string, r: { reservas_liberadas: number }) {
    let alertas = 0;
    const reservas = await this.prisma.inm_operaciones.findMany({
      where: { empresa_id, estado: 'RESERVADA', reserva_vence: { lte: new Date(`${sumarDias(hoy, 2)}T00:00:00Z`) } },
      include: {
        inm_unidades: { select: { codigo: true } },
        inm_operacion_cuotas: { where: { tipo: 'SENA' }, select: { monto_cobrado: true } },
      },
    });
    for (const op of reservas) {
      const vence = isoDia(op.reserva_vence);
      const senaCobrada = op.inm_operacion_cuotas.some((c) => Number(c.monto_cobrado) > 0);
      if (vence < hoy && !senaCobrada) {
        await this.operaciones.desistir(op.id, { motivo: `Reserva vencida el ${vence} sin seña cobrada` }, empresa_id);
        await this.alertas.resolver(empresa_id, `inm:reserva:${op.id}`, 'auto');
        r.reservas_liberadas++;
        continue;
      }
      await this.alertas.registrar({
        empresaId: empresa_id,
        clave: `inm:reserva:${op.id}`,
        tipo: 'inm_reserva_vence',
        criticidad: vence < hoy ? 'alta' : 'media',
        titulo:
          vence < hoy
            ? `Reserva vencida: ${op.inm_unidades.codigo} (${op.numero})`
            : `Reserva por vencer: ${op.inm_unidades.codigo} (${op.numero})`,
        descripcion:
          vence < hoy
            ? `La reserva venció el ${vence} y la seña está cobrada. Firmá el boleto, prorrogá la reserva o registrá el desistimiento.`
            : `La reserva vence el ${vence}. Firmá el boleto o prorrogala.`,
        accionSugerida: 'Abrir la operación',
        modulo: 'INMOBILIARIO',
        privilegioRequerido: 'INM_OPE_OPERACION_VER',
        ruta: `/inmobiliario/operaciones/${op.id}`,
        entidadTipo: 'inm_operaciones',
        entidadId: op.id,
        origen: 'regla',
      });
      alertas++;
    }
    return alertas;
  }

  private async atrasosYEscritura(empresa_id: string, umbralCuotas: number, pctEscritura: number, tasaMora: number) {
    let alertas = 0;
    const ops = await this.prisma.inm_operaciones.findMany({
      where: { empresa_id, estado: { in: ['BOLETO_FIRMADO', 'ESCRITURADA', 'ENTREGADA', 'CANCELADA_TOTAL'] } },
      include: {
        moneda: { select: { codigo: true } },
        inm_unidades: { select: { codigo: true, tipo: true } },
        inm_operacion_cuotas: { include: { inm_hitos: { select: { id: true, fecha_cumplido: true } } } },
      },
    });
    for (const op of ops) {
      const { resumen } = this.operaciones.estadoCuenta(op, tasaMora);
      const claveRescision = `inm:rescision:${op.id}`;
      if (resumen.cuotas_vencidas >= umbralCuotas) {
        await this.alertas.registrar({
          empresaId: empresa_id,
          clave: claveRescision,
          tipo: 'inm_alerta_rescision',
          criticidad: 'alta',
          titulo: `${resumen.cuotas_vencidas} cuotas vencidas: ${op.inm_unidades.codigo} (${op.numero})`,
          descripcion: `La operación acumula ${resumen.cuotas_vencidas} cuotas vencidas (${resumen.vencido} ${op.moneda?.codigo}), con ${resumen.dias_atraso_max} días de atraso. Alcanza el umbral para evaluar la rescisión.`,
          accionSugerida: 'Gestionar el cobro o evaluar la rescisión',
          modulo: 'INMOBILIARIO',
          privilegioRequerido: 'INM_OPE_OPERACION_VER',
          ruta: `/inmobiliario/operaciones/${op.id}`,
          entidadTipo: 'inm_operaciones',
          entidadId: op.id,
          origen: 'regla',
        });
        alertas++;
      } else {
        await this.alertas.resolver(empresa_id, claveRescision, 'auto');
      }

      // Spec §4.1 (lotes): pasado el umbral pagado (25 % por defecto) el comprador puede exigir la escritura.
      if (
        op.inm_unidades.tipo === 'LOTE' &&
        op.estado !== 'ESCRITURADA' &&
        op.estado !== 'ENTREGADA' &&
        resumen.pct_pagado >= pctEscritura
      ) {
        await this.alertas.registrar({
          empresaId: empresa_id,
          clave: `inm:escritura:${op.id}`,
          tipo: 'inm_aviso_escritura',
          criticidad: 'media',
          titulo: `${op.inm_unidades.codigo}: el comprador ya puede exigir la escritura`,
          descripcion: `La operación ${op.numero} lleva pagado el ${resumen.pct_pagado} % (umbral ${pctEscritura} %).`,
          accionSugerida: 'Iniciar la escrituración',
          modulo: 'INMOBILIARIO',
          privilegioRequerido: 'INM_OPE_OPERACION_VER',
          ruta: `/inmobiliario/operaciones/${op.id}`,
          entidadTipo: 'inm_operaciones',
          entidadId: op.id,
          origen: 'regla',
        });
        alertas++;
      }
    }
    return alertas;
  }
}
