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 PromesasKpi {
  total: number;
  pendientes: number;
  cumplidas: number;
  incumplidas: number;
  tasa_cumplimiento: number;
}

interface GestionesKpi {
  total_30d: number;
  por_canal: Array<{ canal: string; cantidad: number }>;
  por_cobrador: Array<{ cobrador_id: string | null; cobrador_nombre: string; cantidad: number }>;
  por_dia: Array<{ fecha: string; cantidad: number }>;
}

interface TopMoroso {
  cliente_id: string;
  cliente_nombre: string;
  saldo_pendiente: number;
  dias_mora: number;
  moneda: string;
}

interface DashboardResponse {
  rango_dias: number;
  cartera: {
    saldo_total: number;
    saldo_vencido: number;
    cuentas_total: number;
    cuentas_con_mora: number;
    tasa_mora_pct: number;
    moneda: string;
  };
  promesas: PromesasKpi;
  gestiones: GestionesKpi;
  top_morosos: TopMoroso[];
  fuente: string;
  modo: string;
}

@Injectable()
export class CarteraDashboardService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: CarteraConfigService,
    private readonly erpNovasisFuente: ErpNovasisFuente,
    private readonly carteraImportadaFuente: CarteraImportadaFuente,
  ) {}

  async getDashboard(empresa_id: string, rangoDias = 30): Promise<DashboardResponse> {
    const config = await this.configService.getOrCreate(empresa_id);
    const hace = new Date(Date.now() - rangoDias * 86_400_000);

    const fuente =
      config.fuente_default === 'cartera-importada'
        ? this.carteraImportadaFuente
        : this.erpNovasisFuente;

    const cuentas = await fuente.listarCuentasPendientes({
      empresa_id,
      page: 1,
      page_size: 500,
    });

    const monedaMain = cuentas[0]?.moneda_codigo ?? 'PYG';
    const saldoTotal = cuentas.reduce((s, c) => s + Number(c.saldo_pendiente), 0);
    const conMora = cuentas.filter((c) => c.dias_mora > 0);
    const saldoVencido = conMora.reduce((s, c) => s + Number(c.saldo_pendiente), 0);

    const [promesas, gestiones] = await Promise.all([
      this.cargarPromesas(empresa_id, hace, config.fuente_default),
      this.cargarGestiones(empresa_id, hace, config.fuente_default),
    ]);

    const topMorosos: TopMoroso[] = [...conMora]
      .sort((a, b) => b.dias_mora - a.dias_mora || b.saldo_pendiente - a.saldo_pendiente)
      .slice(0, 10)
      .map((c) => ({
        cliente_id: c.cliente_id,
        cliente_nombre: c.cliente_nombre,
        saldo_pendiente: Number(c.saldo_pendiente),
        dias_mora: c.dias_mora,
        moneda: c.moneda_codigo,
      }));

    return {
      rango_dias: rangoDias,
      cartera: {
        saldo_total: saldoTotal,
        saldo_vencido: saldoVencido,
        cuentas_total: cuentas.length,
        cuentas_con_mora: conMora.length,
        tasa_mora_pct:
          cuentas.length > 0 ? Math.round((conMora.length / cuentas.length) * 100) : 0,
        moneda: monedaMain,
      },
      promesas,
      gestiones,
      top_morosos: topMorosos,
      fuente: config.fuente_default,
      modo: config.modo_operativo,
    };
  }

  private async cargarPromesas(
    empresa_id: string,
    hace: Date,
    fuente: string,
  ): Promise<PromesasKpi> {
    if (fuente === 'cartera-importada') {
      // Cartera importada no tiene tabla de promesas formales.
      return {
        total: 0,
        pendientes: 0,
        cumplidas: 0,
        incumplidas: 0,
        tasa_cumplimiento: 0,
      };
    }

    const [pendientes, cumplidas, incumplidas] = await Promise.all([
      this.prisma.promesas_pago.count({
        where: { empresa_id, estado: 'pendiente', created_at: { gte: hace } },
      }),
      this.prisma.promesas_pago.count({
        where: { empresa_id, estado: 'cumplida', created_at: { gte: hace } },
      }),
      this.prisma.promesas_pago.count({
        where: { empresa_id, estado: 'incumplida', created_at: { gte: hace } },
      }),
    ]);
    const cerradas = cumplidas + incumplidas;
    const tasa = cerradas > 0 ? Math.round((cumplidas / cerradas) * 100) : 0;
    return {
      total: pendientes + cumplidas + incumplidas,
      pendientes,
      cumplidas,
      incumplidas,
      tasa_cumplimiento: tasa,
    };
  }

  private async cargarGestiones(
    empresa_id: string,
    hace: Date,
    fuente: string,
  ): Promise<GestionesKpi> {
    if (fuente === 'cartera-importada') {
      const rows = await this.prisma.cob_gestion_cartera.findMany({
        where: { empresa_id, anulada: false, fecha: { gte: hace } },
        select: { tipo_gestion: true, fecha: true, usuario_id: true },
      });
      return this.consolidarGestiones(rows, [], hace);
    }

    const rows = await this.prisma.cob_gestion.findMany({
      where: { empresa_id, anulada: false, fecha: { gte: hace } },
      select: {
        tipo_gestion: true,
        fecha: true,
        usuario_id: true,
        cliente: { select: { cobrador_id: true } },
      },
    });

    // Cargar nombres de cobradores únicos.
    const cobradorIds = [...new Set(rows.map((r) => r.cliente?.cobrador_id).filter(Boolean) as string[])];
    const cobradoresMap = new Map<string, string>();
    if (cobradorIds.length > 0) {
      const cobs = await this.prisma.vendedores_cobradores.findMany({
        where: { id: { in: cobradorIds } },
        select: { id: true, nombre: true, apellido: true },
      });
      for (const c of cobs) {
        cobradoresMap.set(c.id, `${c.nombre ?? ''} ${c.apellido ?? ''}`.trim() || 'Sin nombre');
      }
    }

    return this.consolidarGestiones(
      rows.map((r) => ({
        tipo_gestion: r.tipo_gestion as string,
        fecha: r.fecha,
        cobrador_id: r.cliente?.cobrador_id ?? null,
        cobrador_nombre: r.cliente?.cobrador_id
          ? cobradoresMap.get(r.cliente.cobrador_id) ?? 'Desconocido'
          : null,
      })),
      [],
      hace,
    );
  }

  private consolidarGestiones(
    rows: Array<{ tipo_gestion: string; fecha: Date; cobrador_id?: string | null; cobrador_nombre?: string | null }>,
    _placeholder: unknown[],
    hace: Date,
  ): GestionesKpi {
    const total = rows.length;

    const canalCount = new Map<string, number>();
    for (const r of rows) {
      canalCount.set(r.tipo_gestion, (canalCount.get(r.tipo_gestion) ?? 0) + 1);
    }
    const por_canal = [...canalCount.entries()]
      .map(([canal, cantidad]) => ({ canal, cantidad }))
      .sort((a, b) => b.cantidad - a.cantidad);

    const cobradorCount = new Map<string, { nombre: string; cantidad: number }>();
    for (const r of rows) {
      if (!r.cobrador_id) continue;
      const existing = cobradorCount.get(r.cobrador_id);
      if (existing) existing.cantidad++;
      else cobradorCount.set(r.cobrador_id, { nombre: r.cobrador_nombre ?? '', cantidad: 1 });
    }
    const por_cobrador = [...cobradorCount.entries()]
      .map(([id, v]) => ({ cobrador_id: id, cobrador_nombre: v.nombre, cantidad: v.cantidad }))
      .sort((a, b) => b.cantidad - a.cantidad)
      .slice(0, 10);

    // Histograma por día (timezone server).
    const diaCount = new Map<string, number>();
    for (const r of rows) {
      const d = r.fecha;
      const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
      diaCount.set(key, (diaCount.get(key) ?? 0) + 1);
    }
    // Rellenar todos los días del rango con 0 para que el chart se vea consistente.
    const dias: Array<{ fecha: string; cantidad: number }> = [];
    const ahora = new Date();
    const inicio = new Date(hace);
    const cur = new Date(inicio.getFullYear(), inicio.getMonth(), inicio.getDate());
    while (cur <= ahora) {
      const key = `${cur.getFullYear()}-${String(cur.getMonth() + 1).padStart(2, '0')}-${String(cur.getDate()).padStart(2, '0')}`;
      dias.push({ fecha: key, cantidad: diaCount.get(key) ?? 0 });
      cur.setDate(cur.getDate() + 1);
    }

    return {
      total_30d: total,
      por_canal,
      por_cobrador,
      por_dia: dias,
    };
  }
}
