import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from 'src/prisma/prisma.service';
import {
  CreateFechaEspecialDto, FechaComercialDto, UpdateFechaEspecialDto,
} from './dto/fecha-especial.dto';
import { FECHAS_COMERCIALES_PY } from './fechas-comerciales.seed';
import { cuandoEnPalabras, debeAvisar, diasHasta, proximaOcurrencia } from './fechas-especiales.util';

/**
 * Fechas especiales y comerciales (ticket CRM-20, pedido del piloto AGOGO).
 *
 * En F1 el aviso es una **actividad** en la agenda del vendedor. El mensaje
 * automático de WhatsApp es F2: acá sólo se deja anotado el canal preferido para
 * que cuando exista la plantilla no haya que volver a preguntar.
 */
@Injectable()
export class FechasEspecialesService {
  private readonly logger = new Logger(FechasEspecialesService.name);

  constructor(private readonly prisma: PrismaService) {}

  // ── Fechas de un cliente o lead ─────────────────────────────────────────────

  async create(dto: CreateFechaEspecialDto, empresa_id: string, user_id?: string) {
    if (!dto.cliente_id === !dto.lead_id) {
      throw new BadRequestException('La fecha tiene que colgar de un cliente o de un lead, no de ambos ni de ninguno');
    }
    await this.verificarDueno(dto.cliente_id, dto.lead_id, empresa_id);

    return this.prisma.crm_fechas_especiales.create({
      data: {
        empresa_id,
        cliente_id: dto.cliente_id ?? null,
        lead_id: dto.lead_id ?? null,
        tipo: dto.tipo,
        descripcion: dto.descripcion?.trim() || null,
        destinatario_nombre: dto.destinatario_nombre?.trim() || null,
        dia: dto.dia,
        mes: dto.mes,
        anio: dto.anio ?? null,
        recordar_dias_antes: dto.recordar_dias_antes ?? 7,
        canal_preferido: dto.canal_preferido ?? null,
        created_by: user_id ?? null,
      },
    });
  }

  async update(id: string, dto: UpdateFechaEspecialDto, empresa_id: string) {
    await this.deEmpresa(id, empresa_id);
    return this.prisma.crm_fechas_especiales.update({
      where: { id },
      data: { ...dto, updated_at: new Date() },
    });
  }

  async remove(id: string, empresa_id: string) {
    await this.deEmpresa(id, empresa_id);
    await this.prisma.crm_fechas_especiales.delete({ where: { id } });
    return { message: 'Fecha eliminada' };
  }

  /** Las fechas de un cliente o lead, con los días que faltan ya calculados. */
  async findPorDueno(empresa_id: string, cliente_id?: string, lead_id?: string) {
    if (!cliente_id && !lead_id) throw new BadRequestException('Indicá el cliente o el lead');
    const filas = await this.prisma.crm_fechas_especiales.findMany({
      where: { empresa_id, ...(cliente_id ? { cliente_id } : {}), ...(lead_id ? { lead_id } : {}) },
      orderBy: [{ mes: 'asc' }, { dia: 'asc' }],
    });
    const hoy = new Date();
    return filas.map((f) => ({ ...f, dias_restantes: diasHasta(f.dia, f.mes, hoy) }));
  }

  /**
   * Agenda de los próximos N días: es la pantalla que pidió AGOGO ("qué se
   * festeja esta semana"). Ordena por cercanía, no por mes, porque en enero las
   * fechas de diciembre están más lejos que las de febrero.
   */
  async agenda(empresa_id: string, dias = 30) {
    const hoy = new Date();
    const [especiales, comerciales] = await Promise.all([
      this.prisma.crm_fechas_especiales.findMany({
        where: { empresa_id, activo: true },
        include: {
          clientes: { include: { personas: { select: { razon_social: true, telefono: true } } } },
          crm_leads: { select: { id: true, nombre: true, telefono_e164: true } },
        },
      }),
      this.prisma.crm_fechas_comerciales.findMany({ where: { empresa_id, activo: true } }),
    ]);

    const items = [
      ...especiales.map((f) => ({
        id: f.id,
        clase: 'especial' as const,
        tipo: f.tipo,
        titulo: f.descripcion || this.tituloPorDefecto(f.tipo),
        destinatario: f.destinatario_nombre || f.clientes?.personas?.razon_social || f.crm_leads?.nombre || '',
        cliente_id: f.cliente_id,
        lead_id: f.lead_id,
        telefono: f.clientes?.personas?.telefono ?? f.crm_leads?.telefono_e164 ?? null,
        canal_preferido: f.canal_preferido,
        dia: f.dia,
        mes: f.mes,
        dias_restantes: diasHasta(f.dia, f.mes, hoy),
      })),
      ...comerciales.map((f) => ({
        id: f.id,
        clase: 'comercial' as const,
        tipo: 'comercial',
        titulo: f.nombre,
        destinatario: '',
        cliente_id: null,
        lead_id: null,
        telefono: null,
        canal_preferido: null,
        dia: f.dia,
        mes: f.mes,
        dias_restantes: diasHasta(f.dia, f.mes, hoy),
      })),
    ];

    return {
      data: items
        .filter((i) => i.dias_restantes <= dias)
        .sort((a, b) => a.dias_restantes - b.dias_restantes),
    };
  }

  // ── Calendario comercial de la empresa ──────────────────────────────────────

  async findComerciales(empresa_id: string) {
    const existentes = await this.prisma.crm_fechas_comerciales.count({ where: { empresa_id } });
    // Primera entrada a la pantalla: se siembra el calendario paraguayo. Igual
    // criterio que el resto de la configuración del CRM (CRM-04), para que nadie
    // tenga que cargar la Navidad a mano.
    if (existentes === 0) await this.sembrarComerciales(empresa_id);

    const hoy = new Date();
    const filas = await this.prisma.crm_fechas_comerciales.findMany({
      where: { empresa_id },
      orderBy: [{ mes: 'asc' }, { dia: 'asc' }],
    });
    return { data: filas.map((f) => ({ ...f, dias_restantes: diasHasta(f.dia, f.mes, hoy) })) };
  }

  async upsertComercial(dto: FechaComercialDto, empresa_id: string, user_id?: string) {
    return this.prisma.crm_fechas_comerciales.upsert({
      where: { empresa_id_nombre: { empresa_id, nombre: dto.nombre.trim() } },
      create: {
        empresa_id,
        nombre: dto.nombre.trim(),
        dia: dto.dia,
        mes: dto.mes,
        recordar_dias_antes: dto.recordar_dias_antes ?? 14,
        activo: dto.activo ?? true,
        created_by: user_id ?? null,
      },
      update: {
        dia: dto.dia,
        mes: dto.mes,
        ...(dto.recordar_dias_antes !== undefined ? { recordar_dias_antes: dto.recordar_dias_antes } : {}),
        ...(dto.activo !== undefined ? { activo: dto.activo } : {}),
        updated_at: new Date(),
      },
    });
  }

  async removeComercial(id: string, empresa_id: string) {
    const fila = await this.prisma.crm_fechas_comerciales.findFirst({ where: { id, empresa_id } });
    if (!fila) throw new NotFoundException('Fecha comercial no encontrada');
    await this.prisma.crm_fechas_comerciales.delete({ where: { id } });
    return { message: 'Fecha comercial eliminada' };
  }

  /** Idempotente: agrega lo que falta y no pisa lo que la empresa ya editó. */
  async sembrarComerciales(empresa_id: string) {
    const creadas = await this.prisma.crm_fechas_comerciales.createMany({
      data: FECHAS_COMERCIALES_PY.map((f) => ({ empresa_id, ...f })),
      skipDuplicates: true,
    });
    if (creadas.count) this.logger.log(`Calendario comercial sembrado para ${empresa_id}: ${creadas.count} fechas`);
    return creadas.count;
  }

  // ── Job diario ──────────────────────────────────────────────────────────────

  /**
   * Genera una actividad por cada fecha que entra en su ventana de aviso.
   *
   * Es idempotente por `ultimo_aviso_anio`: corra una o diez veces el mismo día,
   * la actividad se crea una sola vez por temporada.
   */
  async generarAvisos(hoy: Date = new Date()): Promise<{ especiales: number; comerciales: number }> {
    let especiales = 0;
    let comerciales = 0;

    const candidatas = await this.prisma.crm_fechas_especiales.findMany({
      where: { activo: true },
      include: {
        clientes: {
          include: {
            personas: { select: { razon_social: true } },
            // `clientes` no tiene vendedor propio; el único responsable que guarda
            // es el cobrador por defecto, que en la práctica es quien lo atiende.
            cobrador: { select: { id: true, usuario_id: true } },
          },
        },
        crm_leads: { select: { nombre: true, asignado_a: true } },
      },
    });

    for (const f of candidatas) {
      if (!debeAvisar(f, hoy)) continue;
      const dias = diasHasta(f.dia, f.mes, hoy);
      const quien = f.destinatario_nombre || f.clientes?.personas?.razon_social || f.crm_leads?.nombre || 'el cliente';
      const vendedor_id = f.clientes?.cobrador?.id ?? f.crm_leads?.asignado_a ?? null;

      await this.prisma.crm_actividades.create({
        data: {
          empresa_id: f.empresa_id,
          tipo: f.canal_preferido === 'visita' ? 'visita' : f.canal_preferido === 'llamada' ? 'llamada' : 'tarea',
          asunto: `${this.tituloPorDefecto(f.tipo)} de ${quien} ${cuandoEnPalabras(dias)}`.slice(0, 200),
          detalle: f.descripcion || null,
          cliente_id: f.cliente_id,
          lead_id: f.lead_id,
          vendedor_id,
          usuario_id: f.clientes?.cobrador?.usuario_id ?? null,
          // Programada para el día del evento, no para hoy: en la agenda tiene
          // que aparecer cuando hay que saludar.
          fecha_programada: proximaOcurrencia(f.dia, f.mes, hoy),
        },
      });

      await this.prisma.crm_fechas_especiales.update({
        where: { id: f.id },
        data: { ultimo_aviso_anio: proximaOcurrencia(f.dia, f.mes, hoy).getFullYear() },
      });
      especiales++;
    }

    const fechasEmpresa = await this.prisma.crm_fechas_comerciales.findMany({ where: { activo: true } });
    for (const f of fechasEmpresa) {
      if (!debeAvisar(f, hoy)) continue;
      const dias = diasHasta(f.dia, f.mes, hoy);

      // Una sola actividad por empresa, sin cliente: es una campaña, no un saludo.
      // Repartirla por cliente generaría cientos de tareas idénticas.
      await this.prisma.crm_actividades.create({
        data: {
          empresa_id: f.empresa_id,
          tipo: 'tarea',
          asunto: `${f.nombre} ${cuandoEnPalabras(dias)}: preparar la campaña`.slice(0, 200),
          detalle: 'Fecha comercial del calendario de la empresa. Definí la promoción y a quiénes contactar.',
          fecha_programada: proximaOcurrencia(f.dia, f.mes, hoy),
        },
      });
      await this.prisma.crm_fechas_comerciales.update({
        where: { id: f.id },
        data: { ultimo_aviso_anio: proximaOcurrencia(f.dia, f.mes, hoy).getFullYear() },
      });
      comerciales++;
    }

    if (especiales || comerciales) {
      this.logger.log(`Avisos de fechas generados: ${especiales} especiales, ${comerciales} comerciales`);
    }
    return { especiales, comerciales };
  }

  // ---------------------------------------------------------------------------

  private tituloPorDefecto(tipo: string): string {
    if (tipo === 'cumpleanos') return 'Cumpleaños';
    if (tipo === 'aniversario') return 'Aniversario';
    return 'Fecha especial';
  }

  private async deEmpresa(id: string, empresa_id: string) {
    const fila = await this.prisma.crm_fechas_especiales.findFirst({ where: { id, empresa_id } });
    if (!fila) throw new NotFoundException('Fecha no encontrada');
    return fila;
  }

  private async verificarDueno(cliente_id: string | undefined, lead_id: string | undefined, empresa_id: string) {
    if (cliente_id) {
      const cliente = await this.prisma.clientes.findFirst({
        where: { id: cliente_id, deleted: false, personas: { empresa_id } },
        select: { id: true },
      });
      if (!cliente) throw new NotFoundException('El cliente no existe en esta empresa');
    }
    if (lead_id) {
      const lead = await this.prisma.crm_leads.findFirst({ where: { id: lead_id, empresa_id }, select: { id: true } });
      if (!lead) throw new NotFoundException('El lead no existe en esta empresa');
    }
  }
}
