import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CarteraConfigService } from '../config/cartera-config.service';
import { CarteraImportadaFuente } from '../fuentes/cartera-importada-fuente.service';
import { ErpNovasisFuente } from '../fuentes/erp-novasis-fuente.service';

interface SeguimientoPendiente {
  gestion_id: string;
  cuenta_id: string;
  cliente_id: string;
  cliente_nombre: string;
  documento_numero: string;
  proxima_accion: string | null;
  proxima_fecha: Date;
  tipo_gestion: string;
  resultado_anterior: string;
  dias_desde_vencimiento: number; // 0 = hoy, >0 = atrasado
}

interface CuentaCritica {
  cuenta_id: string;
  cliente_id: string;
  cliente_nombre: string;
  documento_numero: string;
  saldo_pendiente: number;
  dias_mora: number;
  moneda_codigo: string;
}

/**
 * "Mi día" — vista operativa del cobrador.
 * Combina seguimientos pendientes (gestiones con próxima_fecha ≤ hoy) y
 * top cuentas con más mora para arrancar el día con el trabajo prioritario.
 */
@Injectable()
export class MiDiaService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: CarteraConfigService,
    private readonly erpNovasisFuente: ErpNovasisFuente,
    private readonly carteraImportadaFuente: CarteraImportadaFuente,
  ) {}

  async getMiDia(empresa_id: string, usuario_id: string, opts: { solo_mis_clientes?: boolean } = {}) {
    const config = await this.configService.getOrCreate(empresa_id);
    const ahora = new Date();
    const hoy = new Date(ahora.getFullYear(), ahora.getMonth(), ahora.getDate());
    const finHoy = new Date(hoy.getTime() + 86_400_000);

    // Si pidió "solo mis clientes" — resolver el cobrador del usuario actual.
    // Si el usuario no es cobrador asignado, los listados vuelven vacíos pero
    // los KPIs se calculan igualmente (sirve para que vea que tiene 0 trabajo).
    let cobradorId: string | null = null;
    let usuarioNoEsCobrador = false;
    if (opts.solo_mis_clientes) {
      cobradorId = await this.resolverCobradorDeUsuario(empresa_id, usuario_id);
      if (!cobradorId) usuarioNoEsCobrador = true;
    }

    const [seguimientos, criticas, kpis] = await Promise.all([
      usuarioNoEsCobrador ? [] : this.cargarSeguimientos(empresa_id, config.fuente_default, ahora, finHoy, cobradorId),
      usuarioNoEsCobrador ? [] : this.cargarCriticas(empresa_id, config.fuente_default, cobradorId),
      this.cargarKpis(empresa_id, config.fuente_default, hoy, cobradorId),
    ]);

    return {
      fuente: config.fuente_default,
      modo: config.modo_operativo,
      fecha_hoy: hoy.toISOString(),
      kpis,
      seguimientos_pendientes: seguimientos,
      cuentas_criticas: criticas,
      filtrado_por_cobrador: !!cobradorId,
      usuario_no_es_cobrador: usuarioNoEsCobrador,
    };
  }

  async resolverCobradorDeUsuario(empresa_id: string, usuario_id: string): Promise<string | null> {
    const c = await this.prisma.vendedores_cobradores.findFirst({
      where: { empresa_id, usuario_id, active: true },
      select: { id: true },
    });
    return c?.id ?? null;
  }

  // ─── Seguimientos pendientes ─────────────────────────────────────────────
  // Incluye TODO lo que vence hasta el fin del día actual:
  //   - Atrasadas (proxima_fecha < ahora pero anterior a hoy)
  //   - Programadas para hoy (cualquier hora del día actual, incluso futuras)
  // El frontend muestra chip "Hoy" o "Atrasado Nd" según corresponda.
  private async cargarSeguimientos(
    empresa_id: string,
    fuente: string,
    ahora: Date,
    finHoy: Date,
    cobradorId: string | null,
  ): Promise<SeguimientoPendiente[]> {
    if (fuente === 'cartera-importada') {
      const rows = await this.prisma.cob_gestion_cartera.findMany({
        where: {
          empresa_id,
          anulada: false,
          proxima_fecha: { not: null, lt: finHoy },
        },
        orderBy: { proxima_fecha: 'asc' },
        take: 50,
      });
      const docIds = [...new Set(rows.map((r) => r.cartera_importada_documento_id))];
      const docs = await this.prisma.cartera_importada_documentos.findMany({
        where: { id: { in: docIds } },
        select: {
          id: true,
          cliente_externo_id: true,
          cliente_nombre: true,
          documento_numero: true,
        },
      });
      const docMap = new Map(docs.map((d) => [d.id, d]));
      return rows
        .map<SeguimientoPendiente | null>((r) => {
          const doc = docMap.get(r.cartera_importada_documento_id);
          if (!doc) return null;
          return {
            gestion_id: r.id,
            cuenta_id: doc.id,
            cliente_id: doc.cliente_externo_id,
            cliente_nombre: doc.cliente_nombre,
            documento_numero: doc.documento_numero,
            proxima_accion: r.proxima_accion,
            proxima_fecha: r.proxima_fecha!,
            tipo_gestion: r.tipo_gestion,
            resultado_anterior: r.resultado,
            dias_desde_vencimiento: this.diasEntre(r.proxima_fecha!, ahora),
          };
        })
        .filter((s): s is SeguimientoPendiente => s !== null);
    }

    // ERP Novasis
    const rows = await this.prisma.cob_gestion.findMany({
      where: {
        empresa_id,
        anulada: false,
        proxima_fecha: { not: null, lt: finHoy },
        factura_cab_id: { not: null },
        ...(cobradorId ? { cliente: { cobrador_id: cobradorId } } : {}),
      },
      orderBy: { proxima_fecha: 'asc' },
      take: 50,
      select: {
        id: true,
        cliente_id: true,
        factura_cab_id: true,
        tipo_gestion: true,
        resultado: true,
        proxima_accion: true,
        proxima_fecha: true,
      },
    });
    if (rows.length === 0) return [];

    const facturaIds = rows
      .map((r) => r.factura_cab_id)
      .filter((id): id is string => id !== null);
    const facturas = await this.prisma.factura_cab.findMany({
      where: { id: { in: facturaIds } },
      select: {
        id: true,
        dest: true,
        dpunexp: true,
        dnumdoc: true,
        cliente_id: true,
        clientes: { select: { nombre_fantasia: true, personas: { select: { razon_social: true } } } },
      },
    });
    const facMap = new Map(facturas.map((f) => [f.id, f]));
    return rows
      .map<SeguimientoPendiente | null>((r) => {
        const f = r.factura_cab_id ? facMap.get(r.factura_cab_id) : null;
        if (!f) return null;
        return {
          gestion_id: r.id,
          cuenta_id: f.id,
          cliente_id: r.cliente_id,
          cliente_nombre: f.clientes.nombre_fantasia ?? f.clientes.personas?.razon_social ?? '',
          documento_numero: `${f.dest}-${f.dpunexp}-${f.dnumdoc}`,
          proxima_accion: r.proxima_accion,
          proxima_fecha: r.proxima_fecha!,
          tipo_gestion: r.tipo_gestion,
          resultado_anterior: r.resultado,
          dias_desde_vencimiento: this.diasEntre(r.proxima_fecha!, ahora),
        };
      })
      .filter((s): s is SeguimientoPendiente => s !== null);
  }

  // ─── Top cuentas críticas (mayor mora) ───────────────────────────────────
  private async cargarCriticas(
    empresa_id: string,
    fuente: string,
    cobradorId: string | null,
  ): Promise<CuentaCritica[]> {
    const fuenteImpl =
      fuente === 'cartera-importada' ? this.carteraImportadaFuente : this.erpNovasisFuente;

    const cuentas = await fuenteImpl.listarCuentasPendientes({
      empresa_id,
      cobrador_id: cobradorId ?? undefined,
      page: 1,
      page_size: 10,
    });
    return cuentas
      .filter((c) => c.dias_mora > 0)
      .slice(0, 10)
      .map((c) => ({
        cuenta_id: c.id,
        cliente_id: c.cliente_id,
        cliente_nombre: c.cliente_nombre,
        documento_numero: c.documento_numero,
        saldo_pendiente: c.saldo_pendiente,
        dias_mora: c.dias_mora,
        moneda_codigo: c.moneda_codigo,
      }));
  }

  // ─── KPIs del día ────────────────────────────────────────────────────────
  private async cargarKpis(empresa_id: string, fuente: string, hoy: Date, cobradorId: string | null) {
    const finHoy = new Date(hoy.getTime() + 86_400_000);
    const inicioHoy = hoy;
    const ahora = new Date();

    if (fuente === 'cartera-importada') {
      const [pendientes, atrasados, gestionesHoy] = await Promise.all([
        this.prisma.cob_gestion_cartera.count({
          where: {
            empresa_id,
            anulada: false,
            proxima_fecha: { gte: inicioHoy, lt: finHoy },
          },
        }),
        this.prisma.cob_gestion_cartera.count({
          where: {
            empresa_id,
            anulada: false,
            proxima_fecha: { not: null, lt: ahora },
          },
        }),
        this.prisma.cob_gestion_cartera.count({
          where: { empresa_id, anulada: false, fecha: { gte: inicioHoy, lt: finHoy } },
        }),
      ]);
      return {
        seguimientos_hoy: pendientes,
        seguimientos_atrasados: Math.max(0, atrasados - pendientes),
        gestiones_realizadas_hoy: gestionesHoy,
      };
    }

    const cobradorFilter = cobradorId ? { cliente: { cobrador_id: cobradorId } } : {};
    const [pendientes, atrasados, gestionesHoy] = await Promise.all([
      this.prisma.cob_gestion.count({
        where: {
          empresa_id,
          anulada: false,
          proxima_fecha: { gte: inicioHoy, lt: finHoy },
          ...cobradorFilter,
        },
      }),
      this.prisma.cob_gestion.count({
        where: {
          empresa_id,
          anulada: false,
          proxima_fecha: { not: null, lt: ahora },
          ...cobradorFilter,
        },
      }),
      this.prisma.cob_gestion.count({
        where: { empresa_id, anulada: false, fecha: { gte: inicioHoy, lt: finHoy }, ...cobradorFilter },
      }),
    ]);

    return {
      seguimientos_hoy: pendientes,
      seguimientos_atrasados: Math.max(0, atrasados - pendientes),
      gestiones_realizadas_hoy: gestionesHoy,
    };
  }

  /**
   * Días calendario entre `fecha` y `ref`. Si ambas caen el mismo día → 0.
   * Si `fecha` es del día anterior → 1. Etc. Valores negativos se aplastan a 0
   * (gestión programada para el futuro pero aún en el día actual).
   */
  private diasEntre(fecha: Date, ref: Date): number {
    const a = new Date(fecha.getFullYear(), fecha.getMonth(), fecha.getDate());
    const b = new Date(ref.getFullYear(), ref.getMonth(), ref.getDate());
    const diff = b.getTime() - a.getTime();
    return Math.max(0, Math.round(diff / 86_400_000));
  }
}
