import {
  Injectable,
  Logger,
  OnModuleInit,
  OnModuleDestroy,
} from "@nestjs/common";
import makeWASocket, {
  DisconnectReason,
  useMultiFileAuthState,
  fetchLatestBaileysVersion,
  WASocket,
  downloadMediaMessage,
  normalizeMessageContent,
} from "@whiskeysockets/baileys";
import { Boom } from "@hapi/boom";
import * as path from "path";
const pino = require("pino");
const QRCode = require("qrcode-terminal");
import { LeadsService } from "../leads/leads.service";
import { ConversacionService } from "../conversacion/conversacion.service";
import { ClaudeService } from "../claude/claude.service";
import { MediaService } from "../media/media.service";
import { WhatsappDebounceService } from "./whatsapp-debounce.service";
import { wrapSocket } from "baileys-antiban";

const ESTADOS_TERMINALES = [
  "completado",
  "no_viable",
  "perdido",
  "fin_viable",
  "fin_no_viable",
];

@Injectable()
export class WhatsappService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(WhatsappService.name);
  private sock: WASocket | null = null;
  private authDir = path.join(process.cwd(), "auth_info_baileys");
  private readonly ultimoMsgPorJid = new Map<string, any>();
  private baileysLogger = pino({ level: "info" });

  constructor(
    private readonly leadsService: LeadsService,
    private readonly conversacionService: ConversacionService,
    private readonly claudeService: ClaudeService,
    private readonly mediaService: MediaService,
    private readonly debounceService: WhatsappDebounceService,
  ) {}

  async onModuleInit() {
    await this.conectar();
  }

  async onModuleDestroy() {
    if (this.sock) {
      this.sock.end(undefined);
    }
  }

  private normalizarTelefono(jid: string): string {
    return jid.split("@")[0].replace(/\D/g, "");
  }

  private calcularDelayEscritura(longitudTexto: number): number {
    const min = 1500;
    const max = 4000;
    const factor = Math.min(longitudTexto / 120, 1);
    return Math.round(min + (max - min) * factor);
  }

  private delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  private async conectar() {
    const { state, saveCreds } = await useMultiFileAuthState(this.authDir);
    const { version } = await fetchLatestBaileysVersion();

    this.baileysLogger = pino({ level: "info" }) as any;

    this.sock = makeWASocket({
      version,
      auth: state,
      printQRInTerminal: false,
      logger: this.baileysLogger,
      markOnlineOnConnect: false,
      generateHighQualityLinkPreview: false,
      syncFullHistory: false,
    });

    this.sock.ev.on("creds.update", saveCreds);

    this.sock.ev.on("connection.update", (update) => {
      const { connection, lastDisconnect, qr } = update;

      if (qr) {
        QRCode.generate(qr, { small: true });
        console.log("\n📲 Escanea este QR con WhatsApp\n");
      }

      if (connection === "close") {
        const shouldReconnect =
          (lastDisconnect?.error as Boom)?.output?.statusCode !==
          DisconnectReason.loggedOut;

        this.logger.warn(
          `Conexion cerrada. Reconectar: ${shouldReconnect}. Razon: ${lastDisconnect?.error?.message}`,
        );

        if (shouldReconnect) {
          setTimeout(() => this.conectar(), 5000);
        } else {
          this.logger.error(
            "Sesion cerrada (logged out). Elimina auth_info_baileys y reinicia.",
          );
        }
      } else if (connection === "open") {
        this.logger.log(
          "✅ WhatsApp conectado. Luisa esta lista para recibir mensajes.",
        );
      }
    });

    this.sock.ev.on("messages.upsert", async ({ messages, type }) => {
      if (type !== "notify") return;

      for (const msg of messages) {
        if (msg.key.fromMe) continue;
        if (!msg.key.remoteJid) continue;
        if (msg.key.remoteJid.includes("@g.us")) continue;

        const telefonoNormalizado = this.normalizarTelefono(msg.key.remoteJid);
        const allowedNumber = process.env.ALLOWED_NUMBER?.replace(/\D/g, "");

        if (allowedNumber && telefonoNormalizado !== allowedNumber) {
          this.logger.debug(
            `Mensaje ignorado: ${telefonoNormalizado} no coincide con ALLOWED_NUMBER`,
          );
          continue;
        }

        await this.encolarMensaje(msg);
      }
    });
  }

  private extraerTextoPlano(msg: any): string | null {
    const msgContent = msg.message;
    if (!msgContent) return null;

    if (msgContent.conversation || msgContent.extendedTextMessage) {
      const texto =
        msgContent.conversation || msgContent.extendedTextMessage?.text || "";
      return texto.trim() ? texto : null;
    }

    return null;
  }

  private crearMsgConTexto(msg: any, texto: string): any {
    return {
      ...msg,
      message: { conversation: texto },
    };
  }

  private async encolarMensaje(msg: any): Promise<void> {
    const jid = msg.key.remoteJid!;
    const textoPlano = this.extraerTextoPlano(msg);

    if (textoPlano === null) {
      await this.procesarMensaje(msg);
      return;
    }

    this.ultimoMsgPorJid.set(jid, msg);

    await this.debounceService.add(jid, textoPlano, async (combinedMessage) => {
      const baseMsg = this.ultimoMsgPorJid.get(jid) ?? msg;
      this.ultimoMsgPorJid.delete(jid);
      await this.procesarMensaje(this.crearMsgConTexto(baseMsg, combinedMessage));
    });
  }

  private async procesarMensaje(msg: any): Promise<void> {
    const jid = msg.key.remoteJid!;

    if (jid.includes("@g.us")) return;

    const telefono = jid.split("@")[0];

    try {
      let lead = await this.leadsService.findOrCreate(telefono);

      if (ESTADOS_TERMINALES.includes(lead.estado_actual)) {
        this.logger.log(
          `Lead id=${lead.id} en estado=${lead.estado_actual}. Ignorando.`,
        );
        return;
      }

      let textoNormalizado = "";
      const msgContent = normalizeMessageContent(msg.message);

      if (!msgContent) return;

      if (msgContent.conversation || msgContent.extendedTextMessage) {
        textoNormalizado =
          msgContent.conversation || msgContent.extendedTextMessage?.text || "";
      } else if (msgContent.audioMessage) {
        const audioMeta = msgContent.audioMessage;
        const mimeType = audioMeta.mimetype || "audio/ogg; codecs=opus";

        this.logger.log(
          `[AUDIO 1/4] Recibido — lead=${lead.id}, ptt=${audioMeta.ptt ?? false}, seconds=${audioMeta.seconds ?? "?"}, mimetype=${mimeType}, fileLength=${audioMeta.fileLength ?? "?"}, url=${audioMeta.url ? "si" : "no"}`,
        );

        if (!this.sock) {
          this.logger.error("[AUDIO 1/4] Socket no disponible para descargar audio");
          return;
        }

        const buffer = await downloadMediaMessage(
          msg as any,
          "buffer",
          {},
          {
            logger: this.baileysLogger,
            reuploadRequest: this.sock.updateMediaMessage,
          },
        );

        const audioBuffer = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer);
        const magicHex = audioBuffer.subarray(0, 4).toString("hex");

        this.logger.log(
          `[AUDIO 1/4] Buffer descargado — size=${audioBuffer.length} bytes, magic_hex=${magicHex}, esperado_ogg=4f676753`,
        );

        textoNormalizado = await this.mediaService.transcribirAudio(
          audioBuffer,
          mimeType,
        );

        this.logger.log(
          `[AUDIO 1/4] Transcripcion recibida en procesarMensaje — "${textoNormalizado.slice(0, 200).replace(/\n/g, "\\n")}"`,
        );
      } else if (msgContent.imageMessage) {
        this.logger.log(
          `Imagen recibida de lead id=${lead.id}. Analizando con Vision...`,
        );

        if (!this.sock) return;

        const buffer = await downloadMediaMessage(
          msg as any,
          "buffer",
          {},
          {
            logger: this.baileysLogger,
            reuploadRequest: this.sock.updateMediaMessage,
          },
        );
        const mimeType = msgContent.imageMessage.mimetype || "image/jpeg";
        textoNormalizado = await this.mediaService.inferirImagen(
          Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer),
          mimeType,
          lead.estado_actual,
        );
        if (msgContent.imageMessage.caption) {
          textoNormalizado = `${msgContent.imageMessage.caption}\n\n[Contenido de la imagen: ${textoNormalizado}]`;
        }
      } else {
        this.logger.log(
          `Tipo de mensaje no soportado de lead id=${lead.id}. Ignorando.`,
        );
        return;
      }

      if (!textoNormalizado.trim()) return;

      this.logger.log(`USUARIO [${telefono}]: ${textoNormalizado}`);

      await this.conversacionService.guardarMensaje(
        lead.id,
        "user",
        textoNormalizado,
      );

      const historial =
        await this.conversacionService.obtenerHistorialComoMessages(lead.id);

      const { respuesta, entidad, viable, nuevoEstado } =
        await this.claudeService.llamarClaude(
          lead,
          historial.slice(0, -1),
          textoNormalizado,
        );

      this.logger.log(`LUISA [${telefono}]: ${respuesta}`);

      if (
        (entidad && Object.keys(entidad).length > 0) ||
        nuevoEstado ||
        (viable !== undefined && viable !== null)
      ) {
        lead = await this.leadsService.persistirTurno(lead.id, entidad ?? {}, {
          nuevoEstado,
          viable,
        });
        this.logger.log(
          `Lead id=${lead.id} en DB — estado=${lead.estado_actual}, espacio=${lead.espacio ?? "-"}, rango_m2=${lead.rango_m2 ?? "-"}, estilo=${lead.estilo ?? "-"}, urgencia=${lead.urgencia ?? "-"}, presupuesto=${lead.presupuesto_declarado ?? "-"}`,
        );
      }

      await this.conversacionService.guardarMensaje(
        lead.id,
        "assistant",
        respuesta,
      );
      await this.enviarMensaje(jid, respuesta);
    } catch (error) {
      this.logger.error(
        `Error procesando mensaje de ${telefono}: ${error.message}`,
        error.stack,
      );
    }
  }

  async enviarMensaje(jid: string, texto: string): Promise<void> {
    if (!this.sock) return;

    try {
      const jidPresencia = jid.includes("@lid")
        ? `${jid.split("@")[0]}@s.whatsapp.net`
        : jid;

      await this.sock.sendPresenceUpdate("composing", jidPresencia);
      await this.delay(this.calcularDelayEscritura(texto.length));
      await this.sock.sendPresenceUpdate("paused", jidPresencia);

      const safeSock = wrapSocket(this.sock);
      await safeSock.sendMessage(jid, { text: texto });
      this.logger.log(`Mensaje enviado a ${jid}`);
    } catch (error) {
      this.logger.error(`Error enviando mensaje a ${jid}: ${error.message}`);
    }
  }

  async enviarApertura(
    telefono: string,
    mensajeApertura: string,
  ): Promise<void> {
    const jid = `${telefono}@s.whatsapp.net`;
    await this.enviarMensaje(jid, mensajeApertura);
  }

  isConectado(): boolean {
    return this.sock !== null;
  }
}
