import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { AlertasEmpresaService } from 'src/alertas-empresa/alertas-empresa.service';
import {
  DIAS_GRACIA_PRUEBA,
  diasRestantesPrueba,
  estadoPrueba,
  type EstadoPrueba,
} from 'src/common/prueba-submodulo';
import { PrismaService } from 'src/prisma/prisma.service';

export interface EstadoAddon {
  codigo: string;
  descripcion: string;
  estado: EstadoPrueba;
  fecha_fin_prueba: Date | null;
  dias_restantes: number | null;
}

/**
 * Período de prueba de los add-ons (ticket CRM-02, spec §12).
 *
 * El mecanismo no es exclusivo del CRM —las columnas viven en `submodulos` y
 * `suscripcion_submodulos`, no en tablas `crm_`—, pero el CRM es su primer
 * consumidor y por eso el servicio arranca acá. Cuando un segundo add-on lo use,
 * mover esto a `src/suscripciones` y dejar un re-export.
 *
 * El estado se DERIVA de la fecha (ver src/common/prueba-submodulo.ts). Acá no
 * se persiste ningún estado, así que no hay nada que pueda quedar desfasado.
 */
@Injectable()
export class CrmPruebaService {
  private readonly logger = new Logger(CrmPruebaService.name);

  /** Avisos antes de que venza, en días. Spec §12. */
  private static readonly DIAS_AVISO = [7, 3, 1];

  constructor(
    private readonly prisma: PrismaService,
    private readonly alertas: AlertasEmpresaService,
  ) {}

  private soloFecha(d: Date): Date {
    return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
  }

  private sumarDias(d: Date, dias: number): Date {
    const base = this.soloFecha(d);
    return new Date(base.getTime() + dias * 86_400_000);
  }

  private async suscripcionVigente(empresa_id: string) {
    const suscripcion = await this.prisma.suscripciones.findFirst({
      where: { empresa_id, estado: { in: ['Activo', 'EnGracia'] } },
      orderBy: { created_at: 'desc' },
      select: { id: true, limite_override: true },
    });
    if (!suscripcion) throw new NotFoundException('La empresa no tiene una suscripción vigente');
    return suscripcion;
  }

  /** Estado de todos los submódulos del CRM para la empresa. Alimenta el banner del panel. */
  async estadoCrm(empresa_id: string): Promise<EstadoAddon[]> {
    const suscripcion = await this.suscripcionVigente(empresa_id);

    const submodulos = await this.prisma.submodulos.findMany({
      where: { active: true, modulos: { codigo: 'CRM' } },
      select: { id: true, codigo: true, descripcion: true },
      orderBy: { codigo: 'asc' },
    });

    const filas = await this.prisma.suscripcion_submodulos.findMany({
      where: { suscripcion_id: suscripcion.id, submodulo_id: { in: submodulos.map((s) => s.id) } },
      select: { submodulo_id: true, fecha_fin_prueba: true, active: true },
    });
    const porSubmodulo = new Map(filas.filter((f) => f.active).map((f) => [f.submodulo_id, f.fecha_fin_prueba]));

    return submodulos.map((s) => {
      const fin = porSubmodulo.get(s.id) ?? null;
      return {
        codigo: s.codigo,
        descripcion: s.descripcion,
        estado: estadoPrueba(fin),
        fecha_fin_prueba: fin,
        dias_restantes: diasRestantesPrueba(fin),
      };
    });
  }

  /**
   * Arranca la prueba de un submódulo. La duración sale, en este orden:
   *   1. `suscripciones.limite_override.dias_prueba[<CODIGO>]` — override por cliente
   *   2. `submodulos.dias_prueba` — default del submódulo
   * Si ninguno define días, el submódulo no ofrece prueba y se rechaza.
   */
  async iniciarPrueba(empresa_id: string, codigo: string, user_id?: string) {
    const suscripcion = await this.suscripcionVigente(empresa_id);

    const submodulo = await this.prisma.submodulos.findFirst({
      where: { codigo, active: true },
      select: { id: true, codigo: true, dias_prueba: true },
    });
    if (!submodulo) throw new NotFoundException(`Submódulo "${codigo}" no existe`);

    const override = (suscripcion.limite_override as Record<string, any> | null)?.dias_prueba?.[codigo];
    const dias = Number.isInteger(override) && override > 0 ? Number(override) : submodulo.dias_prueba;
    if (!dias || dias <= 0) {
      throw new BadRequestException(`El submódulo "${codigo}" no ofrece período de prueba`);
    }

    const existente = await this.prisma.suscripcion_submodulos.findFirst({
      where: { suscripcion_id: suscripcion.id, submodulo_id: submodulo.id },
      select: { id: true, fecha_fin_prueba: true },
    });
    // Una prueba por empresa y submódulo (spec §12). Reabrirla sería regalar
    // meses gratis a quien pida de nuevo el alta.
    if (existente?.fecha_fin_prueba) {
      throw new BadRequestException(`"${codigo}" ya tuvo su período de prueba`);
    }

    const fecha_fin_prueba = this.sumarDias(new Date(), dias);

    const fila = existente
      ? await this.prisma.suscripcion_submodulos.update({
          where: { id: existente.id },
          data: { fecha_fin_prueba, active: true },
        })
      : await this.prisma.suscripcion_submodulos.create({
          data: { suscripcion_id: suscripcion.id, submodulo_id: submodulo.id, fecha_fin_prueba },
        });

    this.logger.log(
      `Prueba iniciada: ${codigo} empresa=${empresa_id} dias=${dias} vence=${fecha_fin_prueba.toISOString().slice(0, 10)}`,
    );
    return { codigo, dias, fecha_fin_prueba, suscripcion_submodulo_id: fila.id };
  }

  /**
   * Cuerpo del job diario. Avisa a 7, 3 y 1 día del vencimiento, el día que la
   * prueba pasa a solo lectura, y el día que se bloquea.
   *
   * No cambia ningún estado: el estado se deriva de la fecha. Si el job no corre
   * un día, lo único que se pierde es el aviso, no la consistencia.
   */
  async avisosDiarios(hoy: Date = new Date()): Promise<{ avisos: number }> {
    const objetivos = new Map<number, Date>();
    for (const d of CrmPruebaService.DIAS_AVISO) objetivos.set(d, this.sumarDias(hoy, d));
    // El día siguiente al vencimiento es cuando el usuario nota el corte.
    const venciaAyer = this.sumarDias(hoy, -1);
    const bloqueaHoy = this.sumarDias(hoy, -(DIAS_GRACIA_PRUEBA + 1));

    const fechas = [...objetivos.values(), venciaAyer, bloqueaHoy];

    const filas = await this.prisma.suscripcion_submodulos.findMany({
      where: { active: true, fecha_fin_prueba: { in: fechas } },
      select: {
        fecha_fin_prueba: true,
        submodulos: { select: { codigo: true, descripcion: true } },
        suscripciones: { select: { empresa_id: true, estado: true } },
      },
    });

    let avisos = 0;
    for (const f of filas) {
      const empresaId = f.suscripciones?.empresa_id;
      const codigo = f.submodulos?.codigo;
      if (!empresaId || !codigo || !f.fecha_fin_prueba) continue;

      const restantes = diasRestantesPrueba(f.fecha_fin_prueba, hoy) ?? 0;
      const estado = estadoPrueba(f.fecha_fin_prueba, hoy);

      let titulo: string;
      let descripcion: string;
      let criticidad: 'alta' | 'media' | 'baja';

      if (estado === 'prueba') {
        criticidad = restantes <= 1 ? 'alta' : restantes <= 3 ? 'media' : 'baja';
        titulo = `La prueba de ${f.submodulos?.descripcion ?? codigo} vence en ${restantes} día(s)`;
        descripcion =
          `El período de prueba termina el ${f.fecha_fin_prueba.toISOString().slice(0, 10)}. ` +
          `Después el módulo queda ${DIAS_GRACIA_PRUEBA} días en solo lectura y luego se bloquea. ` +
          'Los datos se conservan.';
      } else if (estado === 'solo_lectura') {
        criticidad = 'alta';
        titulo = `La prueba de ${f.submodulos?.descripcion ?? codigo} venció`;
        descripcion =
          `El módulo quedó en solo lectura por ${DIAS_GRACIA_PRUEBA} días: se puede consultar y ` +
          'exportar, pero no cargar. Los datos se conservan.';
      } else {
        criticidad = 'alta';
        titulo = `${f.submodulos?.descripcion ?? codigo} quedó bloqueado`;
        descripcion = 'Pasaron los 30 días de solo lectura. Contratalo para recuperar el acceso.';
      }

      // La clave lleva el estado: así el aviso de "vence en 7 días" no tapa al
      // de "vence mañana", pero re-correr el job el mismo día no duplica nada.
      await this.alertas.registrar({
        empresaId,
        clave: `prueba:${codigo}:${estado}:${restantes}`,
        tipo: 'prueba_addon',
        criticidad,
        titulo,
        descripcion,
        accionSugerida: 'Contratar el add-on desde Administración → Suscripción.',
        modulo: 'ADMINISTRACION',
        origen: 'sistema',
        entidadTipo: 'submodulo',
        datos: { codigo, estado, dias_restantes: restantes },
      });
      avisos++;
    }

    if (avisos) this.logger.log(`Avisos de prueba registrados: ${avisos}`);
    return { avisos };
  }
}
