import { calcularChurn, canceladasEnPeriodo, cobroEstaVencido } from './metricas-holding';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { EstadoSuscripcion } from 'src/suscripciones/dto/enum-sucripcion';
import { PrismaService } from '../prisma/prisma.service';

export interface SuscripcionResumen {
  id: string;
  estado: string;
  fecha_fin: string | null;
  dias_restantes: number | null;
  plan_nombre: string | null;
  costo_mensual: number | null;
  bloqueada: boolean;
  limite_override: Record<string, unknown> | null;
}

export interface EmpresaJerarquia {
  id: string;
  ruc: string;
  razon_social: string | null;
  type: string | null;
  parent_id: string | null;
  active: boolean | null;
  suscripcion?: SuscripcionResumen | null;
  comision_porcentaje?: number | string | null;
  children?: EmpresaJerarquia[];
  nivel?: number;
}

export interface ValidacionLimites {
  valido: boolean;
  mensaje?: string;
  limites?: {
    max_subsidiarios?: number;
    subsidiarios_actuales: number;
    max_suscripciones_activas?: number;
    suscripciones_actuales: number;
  };
}

@Injectable()
export class JerarquiaEmpresasService {
  constructor(private prisma: PrismaService) {}

  /**
   * Calcula días restantes hasta fecha_fin
   */
  private calcularDiasRestantes(fechaFin: Date | null): number | null {
    if (!fechaFin) return null;
    return Math.ceil((fechaFin.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
  }

  /**
   * Obtiene el resumen de la suscripción más reciente de una empresa
   */
  private async getSuscripcionResumen(empresaId: string): Promise<SuscripcionResumen | null> {
    const susc = await this.prisma.suscripciones.findFirst({
      where: { empresa_id: empresaId },
      orderBy: { created_at: 'desc' },
      select: {
        id: true,
        estado: true,
        fecha_fin: true,
        costo_mensual: true,
        limite_override: true,
        planes: { select: { descripcion: true, codigo: true } },
      },
    });

    if (!susc) return null;

    const estado = String(susc.estado ?? '');
    const fechaFin = susc.fecha_fin ? new Date(susc.fecha_fin) : null;
    const activos: string[] = [EstadoSuscripcion.Activo, EstadoSuscripcion.EnGracia];

    const rawOverride = susc.limite_override;
    const limiteOverride =
      rawOverride && typeof rawOverride === 'object' && !Array.isArray(rawOverride)
        ? (rawOverride as Record<string, unknown>)
        : null;

    return {
      id: susc.id,
      estado,
      fecha_fin: fechaFin ? fechaFin.toISOString().split('T')[0] : null,
      dias_restantes: this.calcularDiasRestantes(fechaFin),
      plan_nombre: susc.planes?.descripcion ?? susc.planes?.codigo ?? null,
      costo_mensual: susc.costo_mensual ? Number(susc.costo_mensual) : null,
      bloqueada: !activos.includes(estado),
      limite_override: limiteOverride,
    };
  }

  /**
   * Cambia el estado de la suscripción más reciente de una empresa
   */
  async cambiarEstadoSuscripcion(
    empresaId: string,
    nuevoEstado: EstadoSuscripcion,
    observacion?: string,
  ): Promise<{ suscripcion_id: string; estado: string }> {
    const susc = await this.prisma.suscripciones.findFirst({
      where: { empresa_id: empresaId },
      orderBy: { created_at: 'desc' },
      select: { id: true },
    });

    if (!susc) throw new NotFoundException('No se encontró suscripción para esta empresa');

    const updated = await this.prisma.suscripciones.update({
      where: { id: susc.id },
      data: {
        estado: nuevoEstado,
        ...(observacion ? { descripcion: observacion } : {}),
        updated_at: new Date(),
      },
      select: { id: true, estado: true },
    });

    return { suscripcion_id: updated.id, estado: String(updated.estado) };
  }

  /**
   * Obtiene la jerarquía completa de una empresa (todos los descendientes)
   */
  async getJerarquiaCompleta(empresaId: string): Promise<EmpresaJerarquia> {
    try {
      const empresa = await this.prisma.empresas.findUnique({
        where: { id: empresaId },
        select: {
          id: true,
          ruc: true,
          razon_social: true,
          type: true,
          parent_id: true,
          active: true,
        },
      });

      if (!empresa) {
        throw new NotFoundException('Empresa no encontrada');
      }
      console.log('recuperando empresa : ', empresa);
      const empresaConHijos = await this.buildJerarquiaRecursiva(empresa, 0);
      console.log('empresa con hijos : ', empresaConHijos);
      return empresaConHijos;
    } catch (error) {
      if (error instanceof NotFoundException) throw error;
      const message = error instanceof Error ? error.message : 'Error desconocido';
      throw new Error(`Error al crear empresa: ${message}`);
    }
  }

  /**
   * Construye la jerarquía de forma recursiva
   */
  private async buildJerarquiaRecursiva(
    empresa: Omit<EmpresaJerarquia, 'children' | 'nivel' | 'suscripcion'>,
    nivel: number,
    visitedIds: Set<string> = new Set(),
  ): Promise<EmpresaJerarquia> {
    if (visitedIds.has(empresa.id)) {
      console.warn(`Referencia circular detectada para empresa ${empresa.id}`);
      return { ...empresa, children: [], nivel };
    }

    if (nivel > 10) {
      console.warn(`Límite de profundidad alcanzado (${nivel}) para empresa ${empresa.id}`);
      return { ...empresa, children: [], nivel };
    }

    visitedIds.add(empresa.id);

    const [hijos, suscripcion] = await Promise.all([
      this.prisma.empresas.findMany({
        where: { parent_id: empresa.id },
        select: {
          id: true,
          ruc: true,
          razon_social: true,
          type: true,
          parent_id: true,
          active: true,
          comision_porcentaje: true,
        },
        orderBy: { created_at: 'asc' },
      }),
      this.getSuscripcionResumen(empresa.id),
    ]);

    const hijosConDescendientes = await Promise.all(
      hijos.map((hijo) =>
        this.buildJerarquiaRecursiva(
          { ...hijo, comision_porcentaje: hijo.comision_porcentaje ? Number(hijo.comision_porcentaje) : null },
          nivel + 1,
          new Set(visitedIds),
        ),
      ),
    );

    return {
      ...empresa,
      suscripcion,
      children: hijosConDescendientes,
      nivel,
    };
  }

  /**
   * Obtiene solo los hijos directos de una empresa
   */
  async getHijosDirectos(empresaId: string, incluirInactivos = false): Promise<EmpresaJerarquia[]> {
    const hijos = await this.prisma.empresas.findMany({
      where: {
        parent_id: empresaId,
        ...(incluirInactivos ? {} : { active: true }),
      },
      select: {
        id: true,
        ruc: true,
        razon_social: true,
        type: true,
        parent_id: true,
        active: true,
        comision_porcentaje: true,
      },
      orderBy: { razon_social: 'asc' },
    });

    return hijos.map((hijo) => ({
      ...hijo,
      comision_porcentaje: hijo.comision_porcentaje ? Number(hijo.comision_porcentaje) : null,
      nivel: 1,
      suscripcion: undefined,
    }));
  }

  /**
   * Obtiene el holding de cualquier empresa en la jerarquía
   */
  async getHolding(empresaId: string): Promise<EmpresaJerarquia | null> {
    const empresa = await this.prisma.empresas.findUnique({
      where: { id: empresaId },
      select: {
        id: true,
        type: true,
        parent_id: true,
      },
    });

    if (!empresa) {
      throw new NotFoundException('Empresa no encontrada');
    }

    // Si ya es holding
    if (empresa.type === 'holding') {
      return this.getEmpresaBasica(empresaId);
    }

    // Buscar recursivamente hacia arriba
    let currentEmpresaId = empresa.parent_id;
    while (currentEmpresaId) {
      const parent = await this.prisma.empresas.findUnique({
        where: { id: currentEmpresaId },
        select: {
          id: true,
          type: true,
          parent_id: true,
        },
      });

      if (!parent) break;

      if (parent.type === 'holding') {
        return this.getEmpresaBasica(parent.id);
      }

      currentEmpresaId = parent.parent_id;
    }

    return null;
  }

  /**
   * Obtiene información básica de una empresa
   */
  private async getEmpresaBasica(empresaId: string): Promise<EmpresaJerarquia> {
    const empresa = await this.prisma.empresas.findUnique({
      where: { id: empresaId },
      select: {
        id: true,
        ruc: true,
        razon_social: true,
        type: true,
        parent_id: true,
        active: true,
      },
    });

    if (!empresa) {
      throw new NotFoundException('Empresa no encontrada');
    }

    return empresa;
  }

  /**
   * Valida si un reseller puede agregar más subsidiarios
   */
  async validarLimitesReseller(resellerEmpresaId: string): Promise<ValidacionLimites> {
    const reseller = await this.prisma.empresas.findUnique({
      where: { id: resellerEmpresaId },
      select: {
        id: true,
        type: true,
        other_empresas: {
          where: { active: true },
          select: { id: true },
        },
      },
    });

    if (!reseller) {
      throw new NotFoundException('Reseller no encontrado');
    }

    if (reseller.type !== 'reseller' && reseller.type !== 'holding') {
      throw new BadRequestException('La empresa no es un reseller válido');
    }

    const subsidiarios_actuales = reseller.other_empresas.length;
    const suscripciones_actuales = 0; // Por ahora simplificado

    return {
      valido: true,
      limites: {
        subsidiarios_actuales,
        suscripciones_actuales,
      },
    };
  }

  /**
   * Obtiene métricas básicas de un reseller
   */
  async getMetricasReseller(resellerEmpresaId: string) {
    const reseller = await this.prisma.empresas.findUnique({
      where: { id: resellerEmpresaId },
      select: {
        id: true,
        razon_social: true,
        type: true,
        other_empresas: {
          where: { active: true },
          select: {
            id: true,
            razon_social: true,
            ruc: true,
            created_at: true,
          },
        },
      },
    });

    if (!reseller) {
      throw new NotFoundException('Reseller no encontrado');
    }

    const subsidiarios = reseller.other_empresas;
    const totalSubsidiarios = subsidiarios.length;

    return {
      reseller: {
        id: reseller.id,
        razon_social: reseller.razon_social,
        type: reseller.type,
      },
      metricas: {
        total_subsidiarios: totalSubsidiarios,
        total_suscripciones: 0, // Por implementar
        ingresos_mensuales: 0, // Por implementar
        comisiones_pendientes: 0, // Por implementar
        comisiones_pagadas: 0, // Por implementar
      },
      subsidiarios: subsidiarios.map((s) => ({
        id: s.id,
        razon_social: s.razon_social,
        ruc: s.ruc,
        fecha_incorporacion: s.created_at,
        suscripciones_activas: 0, // Por implementar
        valor_mensual: 0, // Por implementar
      })),
    };
  }

  /**
   * Obtiene métricas básicas de un holding
   */
  async getMetricasHolding(holdingEmpresaId: string) {
    const holding = await this.prisma.empresas.findUnique({
      where: { id: holdingEmpresaId },
      select: {
        id: true,
        razon_social: true,
        type: true,
      },
    });

    if (!holding) {
      throw new NotFoundException('Holding no encontrado');
    }

    if (holding.type !== 'holding') {
      throw new BadRequestException('La empresa no es un holding');
    }

    // Obtener jerarquía completa
    const jerarquia = await this.getJerarquiaCompleta(holdingEmpresaId);

    return {
      holding: {
        id: holding.id,
        razon_social: holding.razon_social,
        type: holding.type,
      },
      metricas: {
        empresas_totales: this.contarEmpresasRecursivo(jerarquia),
        subsidiarios_directos: jerarquia.children?.length || 0,
        resellers: jerarquia.children?.filter((h) => h.type === 'reseller').length || 0,
        subsidiarios_finales: jerarquia.children?.filter((h) => h.type === 'subsidiario').length || 0,
        suscripciones_activas: 0, // Por implementar
        ingresos_totales: 0, // Por implementar
      },
      jerarquia,
    };
  }

  /**
   * Dashboard ejecutivo completo para un holding
   */
  async getDashboardHolding(holdingEmpresaId: string) {
    const holding = await this.prisma.empresas.findUnique({
      where: { id: holdingEmpresaId },
      select: { id: true, razon_social: true, type: true },
    });

    if (!holding) throw new NotFoundException('Holding no encontrado');
    if (holding.type !== 'holding') throw new BadRequestException('La empresa no es un holding');

    // Obtener IDs de todas las empresas de la red
    const empresasRed = await this.getEmpresasRedIds(holdingEmpresaId);

    const now = new Date();
    const inicioMes = new Date(now.getFullYear(), now.getMonth(), 1);
    const finMes = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
    const en30Dias = new Date(now);
    en30Dias.setDate(en30Dias.getDate() + 30);
    const en60Dias = new Date(now);
    en60Dias.setDate(en60Dias.getDate() + 60);
    const en90Dias = new Date(now);
    en90Dias.setDate(en90Dias.getDate() + 90);

    // Queries paralelas para máximo rendimiento
    const [suscripcionesAll, comprobantesResumen, comisionesResumen, empresasInfo] = await Promise.all([
      // Todas las suscripciones de la red (última por empresa)
      this.prisma.suscripciones.findMany({
        where: { empresa_id: { in: empresasRed } },
        orderBy: { created_at: 'desc' },
        distinct: ['empresa_id'],
        select: {
          id: true,
          empresa_id: true,
          plan_id: true,
          estado: true,
          costo_mensual: true,
          fecha_inicio: true,
          fecha_fin: true,
          fecha_prox_cobro: true,
          created_at: true,
          limite_override: true,
          planes: { select: { id: true, descripcion: true, codigo: true, precio: true } },
          empresas: { select: { id: true, razon_social: true, ruc: true, type: true } },
        },
      }),

      // Comprobantes de pago agrupados por estado
      this.prisma.comprobante_pago_recibidos.groupBy({
        by: ['estado'],
        where: { empresa_id: { in: empresasRed } },
        _count: true,
        _sum: { monto: true },
      }),

      // Comisiones agrupadas por estado
      this.prisma.comisiones_nuevas.groupBy({
        by: ['estado'],
        where: { empresa_pagadora_id: { in: empresasRed } },
        _count: true,
        _sum: { monto_comision: true },
      }),

      // Info de empresas
      this.prisma.empresas.findMany({
        where: { id: { in: empresasRed } },
        select: { id: true, razon_social: true, type: true, active: true },
      }),
    ]);

    // --- KPIs principales ---
    const suscActivas = suscripcionesAll.filter((s) => s.estado === 'Activo');
    const suscEnGracia = suscripcionesAll.filter((s) => s.estado === 'EnGracia');
    const mrr =
      suscActivas.reduce((sum, s) => sum + Number(s.costo_mensual ?? 0), 0) +
      suscEnGracia.reduce((sum, s) => sum + Number(s.costo_mensual ?? 0), 0);

    // Suscripciones canceladas este mes
    // Churn por fecha de BAJA, no por fecha de alta: una cuenta vieja que se va
    // este mes es el churn del mes, y con el criterio anterior quedaba afuera.
    const suscCanceladasMes = canceladasEnPeriodo(suscripcionesAll, inicioMes, finMes);
    const churnRate = calcularChurn(suscActivas.length, suscCanceladasMes);

    // Estado de suscripciones
    const estadosSusc: Record<string, number> = {};
    for (const s of suscripcionesAll) {
      const est = String(s.estado ?? 'Sin estado');
      estadosSusc[est] = (estadosSusc[est] || 0) + 1;
    }

    // Distribución por plan
    const planDistribucion: Record<string, { plan_id: string; plan_nombre: string; count: number; mrr: number }> = {};
    for (const s of suscActivas) {
      const pid = s.plan_id ?? 'sin_plan';
      const pnombre = s.planes?.descripcion ?? s.planes?.codigo ?? 'Sin plan';
      if (!planDistribucion[pid]) planDistribucion[pid] = { plan_id: pid, plan_nombre: pnombre, count: 0, mrr: 0 };
      planDistribucion[pid].count++;
      planDistribucion[pid].mrr += Number(s.costo_mensual ?? 0);
    }

    // Próximos cobros agrupados por mes
    const cobrosProximos = this.agruparCobrosPorMes(suscActivas, now, en90Dias);
    // Vencido es "ya pasó su día", no "vence hoy". Ver cobroEstaVencido: la
    // columna es DATE y el servidor no corre en UTC.
    const cobrosVencidos = suscActivas.filter((s) => cobroEstaVencido(s.fecha_prox_cobro, now));
    const montoCobrosVencidos = cobrosVencidos.reduce((sum, s) => sum + Number(s.costo_mensual ?? 0), 0);

    // Comprobantes resumen
    const compResumen = {
      pendientes: { count: 0, monto: 0 },
      aprobados: { count: 0, monto: 0 },
      rechazados: { count: 0, monto: 0 },
    };
    for (const c of comprobantesResumen) {
      const est = String(c.estado ?? '').toLowerCase();
      const entry = { count: c._count, monto: Number(c._sum?.monto ?? 0) };
      if (est === 'pendiente') compResumen.pendientes = entry;
      else if (est === 'aprobado') compResumen.aprobados = entry;
      else if (est === 'rechazado') compResumen.rechazados = entry;
    }

    // Comisiones resumen
    const comResumen = { pendientes: { count: 0, monto: 0 }, pagadas: { count: 0, monto: 0 } };
    for (const c of comisionesResumen) {
      const est = String(c.estado ?? '').toLowerCase();
      const entry = { count: c._count, monto: Number(c._sum?.monto_comision ?? 0) };
      if (est === 'pendiente') comResumen.pendientes = entry;
      else if (est === 'pagada' || est === 'pagado') comResumen.pagadas = entry;
    }

    // Empresas info
    // El holding no es cliente de su propia red: no se cuenta a sí mismo en
    // ninguno de estos totales. Las consultas de plata sí lo incluyen en el
    // universo, pero como no tiene suscripción ni comprobantes, no suma nada.
    const empresasClientes = empresasInfo.filter((e) => e.id !== holdingEmpresaId);
    const empresasActivas = empresasClientes.filter((e) => e.active !== false).length;
    const resellersCount = empresasClientes.filter((e) => e.type === 'reseller').length;

    // Tabla de suscripciones detallada
    const tablaSuscripciones = suscripcionesAll.map((s) => {
      const fechaFin = s.fecha_fin ? new Date(s.fecha_fin) : null;
      const diasRestantes = fechaFin ? Math.ceil((fechaFin.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)) : null;
      const rawOverride = s.limite_override;
      const hasOverride = rawOverride && typeof rawOverride === 'object' && !Array.isArray(rawOverride);

      return {
        suscripcion_id: s.id,
        empresa_id: s.empresa_id,
        empresa_nombre: s.empresas?.razon_social ?? '—',
        empresa_ruc: s.empresas?.ruc ?? '—',
        empresa_tipo: s.empresas?.type ?? '—',
        plan_nombre: s.planes?.descripcion ?? s.planes?.codigo ?? '—',
        estado: s.estado,
        costo_mensual: Number(s.costo_mensual ?? 0),
        fecha_inicio: s.fecha_inicio ? new Date(s.fecha_inicio).toISOString().split('T')[0] : null,
        fecha_fin: fechaFin ? fechaFin.toISOString().split('T')[0] : null,
        fecha_prox_cobro: s.fecha_prox_cobro ? new Date(s.fecha_prox_cobro).toISOString().split('T')[0] : null,
        dias_restantes: diasRestantes,
        tiene_override: !!hasOverride,
      };
    });

    return {
      holding: { id: holding.id, razon_social: holding.razon_social },
      kpis: {
        mrr,
        empresas_total: empresasClientes.length,
        empresas_activas: empresasActivas,
        resellers: resellersCount,
        suscripciones_activas: suscActivas.length,
        suscripciones_en_gracia: suscEnGracia.length,
        churn_rate: churnRate,
        comprobantes_pendientes: compResumen.pendientes.count,
        comprobantes_pendientes_monto: compResumen.pendientes.monto,
        comisiones_pendientes: comResumen.pendientes.count,
        comisiones_pendientes_monto: comResumen.pendientes.monto,
        cobros_vencidos: cobrosVencidos.length,
        cobros_vencidos_monto: montoCobrosVencidos,
      },
      estados_suscripcion: estadosSusc,
      distribucion_planes: Object.values(planDistribucion).sort((a, b) => b.count - a.count),
      flujo_caja: cobrosProximos,
      comprobantes: compResumen,
      comisiones: comResumen,
      suscripciones: tablaSuscripciones,
    };
  }

  /**
   * Obtiene IDs de todas las empresas de la red (recursivo)
   */
  private async getEmpresasRedIds(holdingId: string): Promise<string[]> {
    const ids: string[] = [holdingId];
    const queue: string[] = [holdingId];

    while (queue.length > 0) {
      const parentId = queue.shift();
      const hijos = await this.prisma.empresas.findMany({
        where: { parent_id: parentId },
        select: { id: true },
      });
      for (const h of hijos) {
        ids.push(h.id);
        queue.push(h.id);
      }
    }

    return ids;
  }

  /**
   * Agrupa cobros de suscripciones activas por mes
   */
  private agruparCobrosPorMes(
    suscripciones: Array<{ fecha_prox_cobro: Date | null; costo_mensual: unknown }>,
    desde: Date,
    hasta: Date,
  ) {
    const meses: Record<string, { mes: string; count: number; monto: number }> = {};

    for (const s of suscripciones) {
      if (!s.fecha_prox_cobro) continue;
      const fecha = new Date(s.fecha_prox_cobro);
      if (fecha < desde || fecha > hasta) continue;

      const key = `${fecha.getFullYear()}-${String(fecha.getMonth() + 1).padStart(2, '0')}`;
      const mesLabel = fecha.toLocaleDateString('es-PY', { month: 'long', year: 'numeric' });

      if (!meses[key]) meses[key] = { mes: mesLabel, count: 0, monto: 0 };
      meses[key].count++;
      meses[key].monto += Number(s.costo_mensual ?? 0);
    }

    return Object.entries(meses)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([, v]) => v);
  }

  /**
   * Cuenta empresas de forma recursiva
   */
  private contarEmpresasRecursivo(empresa: EmpresaJerarquia): number {
    let count = 1;
    if (empresa.children) {
      for (const hijo of empresa.children) {
        count += this.contarEmpresasRecursivo(hijo);
      }
    }
    return count;
  }
}
