import { Injectable, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { RrhhContextService } from '../shared/rrhh-context.service';

interface EventoRaw {
  empresa_id: string;
  fecha_evento: Date;
  tipo: string;
  relevancia: number;
  titulo: string;
  descripcion: string;
  icono?: string;
  color?: string;
  entidad_tipo?: string;
  entidad_id?: string;
}

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly rrhhContext: RrhhContextService,
  ) {}

  async getEventosRecientes(empresaId: string, dias = 7, usuarioId?: string) {
    const desde = new Date();
    desde.setDate(desde.getDate() - dias);

    const eventos = await this.prisma.ai_timeline_eventos.findMany({
      where: { empresa_id: empresaId, fecha_evento: { gte: desde } },
      orderBy: [{ relevancia: 'desc' }, { fecha_evento: 'desc' }],
      take: 50,
    });

    if (!usuarioId) return eventos;
    const puedeRrhh = await this.rrhhContext.puedeVerRRHH(usuarioId, empresaId);
    return puedeRrhh ? eventos : eventos.filter((e) => !e.tipo?.startsWith('rrhh_'));
  }

  async generarEventosAutomaticos(empresaId: string): Promise<number> {
    const config = await this.prisma.ai_empresa_config.findUnique({
      where: { empresa_id: empresaId },
    });
    const umbralVentaAlta = Number(config?.umbral_venta_alta ?? 1_000_000);

    const ayer = new Date();
    ayer.setDate(ayer.getDate() - 1);

    const eventos: EventoRaw[] = [];

    await Promise.all([
      this._eventosFacturas(empresaId, ayer, umbralVentaAlta, eventos),
      this._eventosCobros(empresaId, ayer, eventos),
      this._eventosCuotasVencidas(empresaId, eventos),
      this._eventosClientesNuevos(empresaId, ayer, eventos),
      this._eventosCierreCajas(empresaId, ayer, eventos),
      this._eventosPromesasIncumplidas(empresaId, eventos),
      this._eventosTesoreria(empresaId, ayer, eventos),
      this._eventosRRHH(empresaId, ayer, eventos),
    ]);

    // Solo guardar eventos con relevancia >= 40
    const eventosRelevantes = eventos.filter((e) => e.relevancia >= 40);

    if (eventosRelevantes.length > 0) {
      await this.prisma.ai_timeline_eventos.createMany({ data: eventosRelevantes });
    }

    this.logger.log(`${eventosRelevantes.length} eventos de timeline generados para empresa ${empresaId}`);
    return eventosRelevantes.length;
  }

  // ── Detectores privados ───────────────────────────────────────

  private async _eventosFacturas(
    empresaId: string,
    desde: Date,
    umbral: number,
    eventos: EventoRaw[],
  ) {
    const facturas = await this.prisma.$queryRaw<{
      id: string;
      total_factura: Prisma.Decimal;
      cliente: string;
      dfeemide: Date;
    }[]>`
      SELECT
        fc.id,
        fc.total_factura,
        p.razon_social AS cliente,
        fc.dfeemide
      FROM factura_cab fc
      JOIN clientes c ON c.id = fc.cliente_id
      JOIN personas p ON p.id = c.persona_id
      WHERE fc.empresa_id = ${empresaId}::uuid
        AND fc.dfeemide >= ${desde}
        AND fc.estado != 'Anulada'
      ORDER BY fc.total_factura DESC
      LIMIT 5
    `;

    for (const f of facturas) {
      const monto = Number(f.total_factura);
      if (monto < umbral * 0.5) continue; // ignorar facturas chicas
      const relevancia = monto >= umbral * 3 ? 90 : monto >= umbral ? 70 : 55;
      eventos.push({
        empresa_id: empresaId,
        fecha_evento: f.dfeemide,
        tipo: 'factura_emitida',
        relevancia,
        titulo: `Factura notable — ${f.cliente}`,
        descripcion: `Se emitió una factura por Gs. ${Math.round(monto).toLocaleString('es-PY')} a ${f.cliente}.`,
        icono: 'receipt_long',
        color: 'info',
        entidad_tipo: 'factura',
        entidad_id: f.id,
      });
    }
  }

  private async _eventosCobros(empresaId: string, desde: Date, eventos: EventoRaw[]) {
    const cobros = await this.prisma.$queryRaw<{
      id: string;
      monto_total: Prisma.Decimal;
      cliente: string;
      cobrador: string;
      fecha_emision: Date;
    }[]>`
      SELECT
        rc.id,
        rc.monto_total,
        p.razon_social AS cliente,
        COALESCE(vc.nombre, 'Sin cobrador') AS cobrador,
        rc.fecha_emision
      FROM recibos_cobro rc
      JOIN clientes c ON c.id = rc.cliente_id
      JOIN personas p ON p.id = c.persona_id
      LEFT JOIN vendedores_cobradores vc ON vc.id = rc.cobrador_id
      WHERE rc.empresa_id = ${empresaId}::uuid
        AND rc.fecha_emision >= ${desde}
        AND rc.estado != 'anulado'
      ORDER BY rc.monto_total DESC
      LIMIT 5
    `;

    for (const co of cobros) {
      const monto = Number(co.monto_total);
      eventos.push({
        empresa_id: empresaId,
        fecha_evento: co.fecha_emision,
        tipo: 'cobro_realizado',
        relevancia: monto > 2_000_000 ? 80 : 60,
        titulo: `Cobro registrado — ${co.cobrador}`,
        descripcion: `${co.cobrador} cobró Gs. ${Math.round(monto).toLocaleString('es-PY')} a ${co.cliente}.`,
        icono: 'payments',
        color: 'success',
        entidad_tipo: 'recibo',
        entidad_id: co.id,
      });
    }
  }

  private async _eventosCuotasVencidas(empresaId: string, eventos: EventoRaw[]) {
    const resultado = await this.prisma.$queryRaw<{ total: bigint; monto: Prisma.Decimal }[]>`
      SELECT
        COUNT(*) AS total,
        COALESCE(SUM(fc.saldo_pendiente), 0) AS monto
      FROM factura_cuotas fc
      JOIN factura_cab cab ON cab.id = fc.factura_cab_id
      WHERE cab.empresa_id = ${empresaId}::uuid
        AND fc.estado = 'pendiente'
        AND fc.dvenccuo < CURRENT_DATE
        AND fc.dvenccuo >= CURRENT_DATE - INTERVAL '1 day'
    `;

    const total = Number(resultado[0]?.total ?? 0);
    const monto = Number(resultado[0]?.monto ?? 0);

    if (total > 0) {
      eventos.push({
        empresa_id: empresaId,
        fecha_evento: new Date(),
        tipo: 'cuotas_vencidas_hoy',
        relevancia: total >= 10 ? 85 : 65,
        titulo: `${total} cuota${total > 1 ? 's' : ''} vencida${total > 1 ? 's' : ''} hoy`,
        descripcion: `Vencieron ${total} cuotas con un total de Gs. ${Math.round(monto).toLocaleString('es-PY')} pendiente de cobro.`,
        icono: 'event_busy',
        color: 'warning',
      });
    }
  }

  private async _eventosClientesNuevos(empresaId: string, desde: Date, eventos: EventoRaw[]) {
    const resultado = await this.prisma.$queryRaw<{ total: bigint }[]>`
      SELECT COUNT(*) AS total
      FROM clientes c
      JOIN personas p ON p.id = c.persona_id
      WHERE p.empresa_id = ${empresaId}::uuid
        AND c.created_at >= ${desde}
        AND c.deleted = false
    `;

    const total = Number(resultado[0]?.total ?? 0);
    if (total > 0) {
      eventos.push({
        empresa_id: empresaId,
        fecha_evento: new Date(),
        tipo: 'clientes_nuevos',
        relevancia: 45,
        titulo: `${total} cliente${total > 1 ? 's' : ''} nuevo${total > 1 ? 's' : ''} registrado${total > 1 ? 's' : ''}`,
        descripcion: `Se incorporaron ${total} cliente${total > 1 ? 's' : ''} nuevo${total > 1 ? 's' : ''} en las últimas 24 horas.`,
        icono: 'person_add',
        color: 'success',
      });
    }
  }

  private async _eventosCierreCajas(empresaId: string, desde: Date, eventos: EventoRaw[]) {
    const cierres = await this.prisma.$queryRaw<{
      id: string;
      diferencia: Prisma.Decimal;
      fecha_cierre: Date;
    }[]>`
      SELECT id, diferencia, fecha_cierre
      FROM sesiones_caja
      WHERE empresa_id = ${empresaId}::uuid
        AND fecha_cierre >= ${desde}
        AND diferencia IS NOT NULL
        AND ABS(diferencia) > 0
      ORDER BY ABS(diferencia) DESC
      LIMIT 3
    `;

    for (const c of cierres) {
      const diff = Number(c.diferencia);
      const esNegativo = diff < 0;
      eventos.push({
        empresa_id: empresaId,
        fecha_evento: c.fecha_cierre,
        tipo: 'cierre_caja_diferencia',
        relevancia: Math.abs(diff) > 100_000 ? 80 : 55,
        titulo: `Cierre de caja con diferencia ${esNegativo ? 'negativa' : 'positiva'}`,
        descripcion: `Se detectó una diferencia de Gs. ${Math.abs(Math.round(diff)).toLocaleString('es-PY')} en el cierre de caja.`,
        icono: 'point_of_sale',
        color: esNegativo ? 'error' : 'warning',
        entidad_tipo: 'sesion_caja',
        entidad_id: c.id,
      });
    }
  }

  private async _eventosTesoreria(empresaId: string, desde: Date, eventos: EventoRaw[]) {
    try {
      // Movimientos grandes del día anterior
      const movimientos = await this.prisma.$queryRaw<{
        id: string;
        tipo: string;
        monto: Prisma.Decimal;
        descripcion: string;
        cuenta: string;
        fecha: Date;
      }[]>`
        SELECT tm.id, tm.tipo, tm.monto, tm.descripcion, tc.nombre AS cuenta, tm.fecha::timestamp AS fecha
        FROM tes_movimientos tm
        JOIN tes_cuentas tc ON tc.id = tm.cuenta_id
        WHERE tm.empresa_id = ${empresaId}::uuid
          AND tm.estado = 'PROCESADO'
          AND tm.fecha::date >= ${desde}::date
          AND tm.monto >= 2000000
        ORDER BY tm.monto DESC
        LIMIT 5
      `;

      for (const mv of movimientos) {
        const monto = Number(mv.monto);
        const esIngreso = mv.tipo === 'INGRESO';
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: mv.fecha,
          tipo: 'movimiento_tesoreria',
          relevancia: monto >= 10_000_000 ? 85 : 65,
          titulo: `${esIngreso ? 'Ingreso' : 'Egreso'} en ${mv.cuenta}`,
          descripcion: `${mv.descripcion || (esIngreso ? 'Ingreso' : 'Egreso')} de Gs. ${Math.round(monto).toLocaleString('es-PY')} en cuenta "${mv.cuenta}".`,
          icono: esIngreso ? 'account_balance' : 'money_off',
          color: esIngreso ? 'success' : 'warning',
          entidad_tipo: 'movimiento_tesoreria',
          entidad_id: mv.id,
        });
      }

      // Cheques rechazados ayer
      const rechazados = await this.prisma.$queryRaw<{
        id: string;
        numero_cheque: string;
        banco_emisor: string;
        monto: Prisma.Decimal;
        updated_at: Date;
      }[]>`
        SELECT id, numero_cheque, banco_emisor, monto, updated_at
        FROM tes_cheques
        WHERE empresa_id = ${empresaId}::uuid
          AND estado = 'RECHAZADO'
          AND updated_at::date >= ${desde}::date
        LIMIT 5
      `;

      for (const ch of rechazados) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: ch.updated_at,
          tipo: 'cheque_rechazado',
          relevancia: 90,
          titulo: `Cheque rechazado por banco`,
          descripcion: `Cheque Nro ${ch.numero_cheque} de ${ch.banco_emisor} por Gs. ${Math.round(Number(ch.monto)).toLocaleString('es-PY')} fue rechazado.`,
          icono: 'money_off_csred',
          color: 'error',
          entidad_tipo: 'cheque',
          entidad_id: ch.id,
        });
      }
    } catch { /* tablas tes_* no existen aún */ }
  }

  private async _eventosRRHH(empresaId: string, desde: Date, eventos: EventoRaw[]) {
    if (!(await this.rrhhContext.tieneModuloRRHH(empresaId))) return;
    const desdeISO = desde.toISOString();

    // Liquidaciones cerradas
    try {
      const liqs = await this.prisma.$queryRaw<{
        id: string; anio: number; mes: number; tipo: string;
        total_neto: Prisma.Decimal; cantidad_empleados: number; updated_at: Date;
      }[]>`
        SELECT id, periodo_anio AS anio, periodo_mes AS mes, tipo,
               total_neto, cantidad_empleados, updated_at
        FROM rrhh_liquidaciones_cabecera
        WHERE empresa_id = ${empresaId}::uuid
          AND estado = 'CERRADA'
          AND updated_at >= ${desdeISO}::timestamp
        ORDER BY updated_at DESC LIMIT 5
      `;
      for (const l of liqs) {
        const monto = Number(l.total_neto);
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: l.updated_at,
          tipo: 'rrhh_liquidacion_cerrada',
          relevancia: 80,
          titulo: `Liquidación ${l.tipo} ${String(l.mes).padStart(2, '0')}/${l.anio} cerrada`,
          descripcion: `Se cerró la liquidación con ${l.cantidad_empleados} empleados. Neto a pagar Gs. ${Math.round(monto).toLocaleString('es-PY')}.`,
          icono: 'task_alt',
          color: 'success',
          entidad_tipo: 'rrhh_liquidacion',
          entidad_id: l.id,
        });
      }
    } catch (e) { this.logger.warn(`timeline rrhh liquidacion: ${(e as Error).message}`); }

    // Acreditaciones bancarias
    try {
      const acr = await this.prisma.$queryRaw<{
        id: string; total: Prisma.Decimal; cantidad_empleados: number; created_at: Date;
      }[]>`
        SELECT id, total, cantidad_empleados, created_at
        FROM rrhh_acreditaciones_bancarias
        WHERE empresa_id = ${empresaId}::uuid
          AND created_at >= ${desdeISO}::timestamp
        ORDER BY created_at DESC LIMIT 3
      `;
      for (const a of acr) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: a.created_at,
          tipo: 'rrhh_acreditacion_generada',
          relevancia: 70,
          titulo: `Acreditación bancaria generada`,
          descripcion: `${a.cantidad_empleados} empleados — Gs. ${Math.round(Number(a.total)).toLocaleString('es-PY')}.`,
          icono: 'account_balance',
          color: 'info',
          entidad_tipo: 'rrhh_acreditacion',
          entidad_id: a.id,
        });
      }
    } catch (e) { this.logger.warn(`timeline rrhh acreditacion: ${(e as Error).message}`); }

    // Reportes IPS
    try {
      const ips = await this.prisma.$queryRaw<{
        id: string; anio: number; mes: number; total_a_depositar: Prisma.Decimal; updated_at: Date;
      }[]>`
        SELECT id, periodo_anio AS anio, periodo_mes AS mes, total_a_depositar, updated_at
        FROM rrhh_reportes_ips
        WHERE empresa_id = ${empresaId}::uuid
          AND estado != 'ANULADO'
          AND updated_at >= ${desdeISO}::timestamp
        ORDER BY updated_at DESC LIMIT 3
      `;
      for (const r of ips) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: r.updated_at,
          tipo: 'rrhh_reporte_ips',
          relevancia: 65,
          titulo: `Reporte IPS ${String(r.mes).padStart(2, '0')}/${r.anio}`,
          descripcion: `Total a depositar Gs. ${Math.round(Number(r.total_a_depositar)).toLocaleString('es-PY')}.`,
          icono: 'description',
          color: 'info',
          entidad_tipo: 'rrhh_reporte_ips',
          entidad_id: r.id,
        });
      }
    } catch (e) { this.logger.warn(`timeline rrhh ips: ${(e as Error).message}`); }

    // Altas/Bajas de empleados
    try {
      const desdeFecha = desde.toISOString().slice(0, 10);
      const altas = await this.prisma.$queryRaw<{ cant: number }[]>`
        SELECT COUNT(*)::int AS cant FROM rrhh_empleados
        WHERE empresa_id = ${empresaId}::uuid
          AND fecha_ingreso >= ${desdeFecha}::date
      `;
      const cant = altas[0]?.cant ?? 0;
      if (cant > 0) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: new Date(),
          tipo: 'rrhh_alta_empleado',
          relevancia: 50,
          titulo: `${cant} alta${cant !== 1 ? 's' : ''} de empleado${cant !== 1 ? 's' : ''}`,
          descripcion: `Se incorporaron ${cant} empleado${cant !== 1 ? 's' : ''} en los últimos días.`,
          icono: 'person_add',
          color: 'success',
        });
      }

      const bajas = await this.prisma.$queryRaw<{ cant: number }[]>`
        SELECT COUNT(*)::int AS cant FROM rrhh_desvinculaciones
        WHERE empresa_id = ${empresaId}::uuid
          AND fecha_ultimo_dia >= ${desdeFecha}::date
      `;
      const cb = bajas[0]?.cant ?? 0;
      if (cb > 0) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: new Date(),
          tipo: 'rrhh_baja_empleado',
          relevancia: 75,
          titulo: `${cb} desvinculación${cb !== 1 ? 'es' : ''}`,
          descripcion: `${cb} empleado${cb !== 1 ? 's' : ''} desvinculado${cb !== 1 ? 's' : ''} en los últimos días.`,
          icono: 'person_remove',
          color: 'warning',
        });
      }
    } catch (e) { this.logger.warn(`timeline rrhh altas/bajas: ${(e as Error).message}`); }

    // Vacaciones
    try {
      const vac = await this.prisma.$queryRaw<{ cant: number }[]>`
        SELECT COUNT(*)::int AS cant FROM rrhh_vacaciones_solicitudes
        WHERE empresa_id = ${empresaId}::uuid
          AND estado = 'APROBADA'
          AND updated_at >= ${desdeISO}::timestamp
      `;
      const c = vac[0]?.cant ?? 0;
      if (c > 0) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: new Date(),
          tipo: 'rrhh_vacaciones_aprobadas',
          relevancia: 45,
          titulo: `${c} solicitud${c !== 1 ? 'es' : ''} de vacaciones aprobada${c !== 1 ? 's' : ''}`,
          descripcion: `Se aprobaron ${c} solicitud${c !== 1 ? 'es' : ''} de vacaciones recientemente.`,
          icono: 'beach_access',
          color: 'info',
        });
      }
    } catch (e) { this.logger.warn(`timeline rrhh vacaciones: ${(e as Error).message}`); }
  }

  private async _eventosPromesasIncumplidas(empresaId: string, eventos: EventoRaw[]) {
    // Solo si existe la tabla promesas_pago
    try {
      const resultado = await this.prisma.$queryRaw<{ total: bigint }[]>`
        SELECT COUNT(*) AS total
        FROM promesas_pago pp
        JOIN factura_cab fc ON fc.id = pp.factura_id
        WHERE fc.empresa_id = ${empresaId}::uuid
          AND pp.fecha_prometida < CURRENT_DATE
          AND pp.estado = 'pendiente'
      `;
      const total = Number(resultado[0]?.total ?? 0);
      if (total > 0) {
        eventos.push({
          empresa_id: empresaId,
          fecha_evento: new Date(),
          tipo: 'promesas_incumplidas',
          relevancia: 75,
          titulo: `${total} promesa${total > 1 ? 's' : ''} de pago incumplida${total > 1 ? 's' : ''}`,
          descripcion: `${total} cliente${total > 1 ? 's' : ''} no cumplió su promesa de pago. Requiere seguimiento.`,
          icono: 'broken_image',
          color: 'error',
        });
      }
    } catch {
      // Tabla no existe aún — silencioso
    }
  }
}
