import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, LessThan } from 'typeorm';
import { Lead, EstadoLead } from './lead.entity';

const SECUENCIA_ESTADOS = [
  'nuevo',
  'apertura',
  'espacio',
  'tamano',
  'estilo',
  'urgencia',
  'presupuesto',
] as const;

const VALORES_POR_ESTADO: Record<string, string[]> = {
  espacio: ['cocina', 'bano', 'salon', 'integral', 'otro'],
  tamano: ['menos10', '10a20', '20a40', 'mas40'],
  estilo: ['funcional', 'cuidado', 'exclusivo'],
  urgencia: ['urgente', 'medio_plazo', 'frio'],
};

const CAMPO_POR_ESTADO: Record<string, keyof Lead> = {
  espacio: 'espacio',
  tamano: 'rango_m2',
  estilo: 'estilo',
  urgencia: 'urgencia',
  presupuesto: 'presupuesto_declarado',
};

@Injectable()
export class LeadsService {
  private readonly logger = new Logger(LeadsService.name);

  constructor(
    @InjectRepository(Lead)
    private readonly leadRepo: Repository<Lead>,
  ) {}

  /**
   * Normaliza estados legacy del scheduler/DB al flujo de cualificacion.
   */
  normalizarEstadoFlujo(estado: string): string {
    if (estado === 'en_proceso' || estado === 'recopilando_datos') {
      return 'apertura';
    }
    return estado;
  }

  getSiguienteEstado(estadoActual: string, viable?: boolean): string {
    const estado = this.normalizarEstadoFlujo(estadoActual);

    if (estado === 'presupuesto') {
      return viable === false ? 'fin_no_viable' : 'fin_viable';
    }

    const idx = SECUENCIA_ESTADOS.indexOf(
      estado as (typeof SECUENCIA_ESTADOS)[number],
    );
    if (idx === -1 || idx >= SECUENCIA_ESTADOS.length - 1) {
      return estado;
    }
    return SECUENCIA_ESTADOS[idx + 1];
  }

  getValoresPermitidos(estado: string): string[] {
    const estadoNorm = this.normalizarEstadoFlujo(estado);
    return VALORES_POR_ESTADO[estadoNorm] ?? [];
  }

  getCampoParaEstado(estado: string): keyof Lead | null {
    const estadoNorm = this.normalizarEstadoFlujo(estado);
    return CAMPO_POR_ESTADO[estadoNorm] ?? null;
  }

  esPresupuestoValido(valor: string): boolean {
    const normalizado = valor.trim().toLowerCase();
    if (!normalizado) return false;
    return /\d/.test(normalizado);
  }

  evaluarViabilidad(presupuesto: string): boolean {
    const numeros = presupuesto.match(/\d[\d.]*/g);
    if (!numeros?.length) return true;

    const valor = parseInt(numeros[0].replace(/\./g, ''), 10);
    if (Number.isNaN(valor)) return true;

    return valor >= 5000;
  }

  /**
   * Busca un lead por número de teléfono.
   * Si no existe, lo crea con estado 'nuevo'.
   */
  async findOrCreate(telefono: string): Promise<Lead> {
    let lead = await this.leadRepo.findOne({ where: { telefono } });
    if (!lead) {
      lead = this.leadRepo.create({ telefono, estado_actual: 'nuevo' });
      lead = await this.leadRepo.save(lead);
      this.logger.log(`Lead nuevo creado: telefono=${telefono}, id=${lead.id}`);
    }
    return lead;
  }

  async findByTelefono(telefono: string): Promise<Lead | null> {
    return this.leadRepo.findOne({ where: { telefono } });
  }

  async findById(id: number): Promise<Lead | null> {
    return this.leadRepo.findOne({ where: { id } });
  }

  async findByEstado(estado: EstadoLead): Promise<Lead[]> {
    return this.leadRepo.find({ where: { estado_actual: estado } });
  }

  async updateEstado(lead: Lead, estado: EstadoLead | string): Promise<Lead> {
    await this.leadRepo.update(lead.id, {
      estado_actual: estado as EstadoLead,
    });
    this.logger.log(`Lead id=${lead.id} estado_actual=${estado}`);
    return this.leadRepo.findOne({ where: { id: lead.id } });
  }

  /**
   * Actualiza campos del lead según el estado actual del flujo.
   * Solo actualiza los campos que se pasan en el partial.
   */
  async updateDatos(leadId: number, datos: Partial<Lead>): Promise<Lead> {
    const campos = Object.keys(datos).filter(
      (k) => datos[k as keyof Lead] !== undefined,
    );
    if (campos.length === 0) {
      return this.leadRepo.findOne({ where: { id: leadId } });
    }

    await this.leadRepo.update(leadId, datos);
    this.logger.log(
      `Lead id=${leadId} datos guardados: ${JSON.stringify(datos)}`,
    );
    return this.leadRepo.findOne({ where: { id: leadId } });
  }

  async marcarViable(lead: Lead, viable: boolean): Promise<Lead> {
    const estado = viable ? 'completado' : 'no_viable';
    await this.leadRepo.update(lead.id, { viable, estado_actual: estado });
    this.logger.log(`Lead id=${lead.id} viable=${viable}, estado=${estado}`);
    return this.leadRepo.findOne({ where: { id: lead.id } });
  }

  /**
   * Persiste datos del lead y cambio de estado en una sola operacion.
   */
  async persistirTurno(
    leadId: number,
    datos: Partial<Lead>,
    options?: { nuevoEstado?: string; viable?: boolean },
  ): Promise<Lead> {
    const patch: Partial<Lead> = { ...datos };

    if (options?.nuevoEstado === 'fin_viable') {
      patch.viable = true;
      patch.estado_actual = 'completado';
    } else if (options?.nuevoEstado === 'fin_no_viable') {
      patch.viable = false;
      patch.estado_actual = 'no_viable';
    } else if (options?.nuevoEstado) {
      patch.estado_actual = options.nuevoEstado as EstadoLead;
    } else if (options?.viable !== undefined && options?.viable !== null) {
      patch.viable = options.viable;
      patch.estado_actual = options.viable ? 'completado' : 'no_viable';
    }

    const campos = Object.keys(patch).filter(
      (k) => patch[k as keyof Lead] !== undefined,
    );
    if (campos.length === 0) {
      return this.leadRepo.findOne({ where: { id: leadId } });
    }

    await this.leadRepo.update(leadId, patch);
    this.logger.log(
      `Lead id=${leadId} persistido: ${JSON.stringify(patch)}`,
    );
    return this.leadRepo.findOne({ where: { id: leadId } });
  }

  /**
   * Marca como perdido cualquier lead en_proceso sin actividad en más de 48h.
   */
  async marcarLeadsPerdidos(): Promise<void> {
    const hace48h = new Date(Date.now() - 48 * 60 * 60 * 1000);
    const leadsSinActividad = await this.leadRepo.find({
      where: {
        estado_actual: 'en_proceso',
        updated_at: LessThan(hace48h),
      },
    });

    for (const lead of leadsSinActividad) {
      lead.estado_actual = 'perdido';
      await this.leadRepo.save(lead);
      this.logger.warn(
        `Lead id=${lead.id} marcado como perdido por inactividad > 48h`,
      );
    }
  }

  async save(lead: Lead): Promise<Lead> {
    return this.leadRepo.save(lead);
  }
}
