"""
Servicio de productos: búsqueda MCP, clasificación de intención y generación de respuestas LLM.
"""
import json as json_mod
import logging
import time

from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_openai import ChatOpenAI
from mcp_integration.tools import get_tools
from agent.chat_products_nodes import _extract_mcp_result
from agent.prompts import (
    MARCI_BASE_IDENTITY,
    SEARCH_EXTRACT_QUERY_PROMPT,
    SEARCH_NO_RESULTS_PROMPT,
    SEARCH_NO_PROMO_AVAILABLE_PROMPT,
    SEARCH_NO_PRODUCT_PROMPT,
    CHAT_CLASSIFICATION_PROMPT,
    SEARCH_EXTRACT_PRODUCT_NAME_PROMPT,
    SEARCH_PRODUCT_DETAIL_PROMPT,
    SEARCH_PRODUCT_COMPARE_PROMPT,
    RESUME_PURCHASE_GREETING_PROMPT,
    GREETING_FOLLOWUP_PROMPT,
)
from config.settings import settings
from ..models import SearchProductItem

logger = logging.getLogger(__name__)

_SEARCH_RESPONSE_PROMPT_WITH_EMOJIS = """{marci_identity}
{greeting_hint}
{history_hint}

El usuario buscó: "{query}"
Se encontraron {total} productos.

PRODUCTOS ENCONTRADOS (solo para tu contexto interno, NO los menciones en texto):
{products_context}

REGLA FUNDAMENTAL: Los productos de la lista arriba están en el catálogo de Marcimex. Preséntales con confianza SOLO si son genuinamente relevantes para lo que el usuario pidió. Si el usuario pidió algo que claramente NO existe en el catálogo (ej: terrenos, casas, carros, ropa) y los productos mostrados son completamente distintos a eso, sé honesto e indica que ese tipo de producto no está disponible en nuestra tienda y ofrece alternativas de nuestras categorías (electrodomésticos, tecnología, muebles, movilidad). NUNCA presentes productos irrelevantes como si fueran lo que el usuario buscó.

INSTRUCCIONES:
1. Escribe UNA respuesta breve y amigable presentando los productos encontrados (1-2 frases máximo).
2. USA EMOJIS RELEVANTES en tu respuesta para hacerla más visual y amigable (ej: 📺 para TV, 📱 para celular, ✨ para destacar).
3. CRÍTICO: NUNCA menciones el nombre específico de ningún producto, marca ni modelo en tu texto (ej: NO digas "Whirlpool X", "Samsung Y", ni ningún nombre). Los productos se muestran como tarjetas visuales — usa referencias genéricas como "estas lavadoras", "estas opciones", "encontramos X opciones".
4. NUNCA incluyas listas de productos, precios, IDs ni especificaciones en tu respuesta de texto.
5. Si hay varias opciones, menciona que hay variedad para elegir (sin nombrar ninguna).
6. CRÍTICO: NUNCA hagas preguntas adicionales ni pidas más información. HAY PRODUCTOS DISPONIBLES — preséntelos directamente.
7. CRÍTICO: NUNCA incluyas texto con formato "[CONTEXTO DEL SISTEMA...]" en tu respuesta.
8. Si la pregunta del usuario es sobre REGALOS (qué regalar a alguien, Día de la Madre, cumpleaños, aniversario, graduación, etc.), presenta los productos con tono festivo y emotivo usando emojis de celebración (🎁 🎉 ✨ 🎊). Ej: "¡Aquí tienes unas opciones perfectas para regalar! 🎁✨" o "¡Estas opciones son ideales como detalle especial! 🎉".
"""

_llm = ChatOpenAI(model=settings.MODEL_NAME, temperature=0)

CLASSIFY_HISTORY_SLICE = 6  # Mensajes del historial enviados al LLM para clasificar intención


def _print_llm_payload(label: str, messages: list):
    """Imprime en consola el payload completo que se envía al LLM."""
    sep = "=" * 70
    logger.debug(f"\n{sep}")
    logger.debug(f"🔷 LLM PAYLOAD — {label}")
    logger.debug(sep)
    for i, msg in enumerate(messages):
        role = msg.__class__.__name__.replace("Message", "").upper()
        content = getattr(msg, "content", "")
        logger.debug(f"\n--- [{i}] {role} ---")
        logger.debug(content)
    logger.debug(f"\n{sep}\n")


class ProductService:
    """Búsqueda de productos, clasificación de intención y generación de respuestas LLM."""

    def classify_intent(self, query: str, history_messages: list) -> str:
        """
        Clasifica la intención del usuario usando el prompt de clasificación.
        Retorna: AGENT, AGENT_DIRECT, BALANCE, PAYMENT, GREETING, PRODUCT_DETAIL, PRODUCT u OTHER.
        """
        classify_messages = [SystemMessage(content=CHAT_CLASSIFICATION_PROMPT)]
        history_slice = history_messages[-CLASSIFY_HISTORY_SLICE:] if history_messages else []
        if history_slice:
            # Strip [CONTEXTO DEL SISTEMA] blocks from AI messages before sending to classifier.
            # These blocks contain product listings that confuse the LLM into generating a
            # conversational response instead of a single classification keyword.
            cleaned = []
            for m in history_slice:
                if isinstance(m, HumanMessage):
                    cleaned.append(m)
                else:
                    content = getattr(m, "content", "")
                    if "[CONTEXTO DEL SISTEMA" in content:
                        content = content.split("[CONTEXTO DEL SISTEMA")[0].strip()
                    cleaned.append(AIMessage(content=content))
            classify_messages.extend(cleaned)
        classify_messages.append(HumanMessage(content=(
            f"{query}\n\n"
            "[TAREA: clasifica el mensaje anterior. "
            "Responde SOLO con una palabra — AGENT / AGENT_DIRECT / PRODUCT_DETAIL / PRODUCT / PAYMENT / BALANCE / GREETING / OTHER]"
        )))

        sys_chars = len(CHAT_CLASSIFICATION_PROMPT)
        hist_chars = sum(len(getattr(m, "content", "")) for m in history_slice)
        query_chars = len(query)
        total_chars = sys_chars + hist_chars + query_chars
        logger.info(
            f"📤 [classify_intent] Enviando al LLM | "
            f"sys_prompt={sys_chars}c (~{sys_chars//4}tok) | "
            f"historial={len(history_slice)} msgs/{hist_chars}c | "
            f"query={query_chars}c | "
            f"TOTAL ~{total_chars//4} tokens estimados"
        )
        logger.info(f"📝 [classify_intent] Pregunta del usuario: '{query}'")
        if history_slice:
            has_product_ctx = any(
                "[CONTEXTO DEL SISTEMA" in getattr(m, "content", "")
                for m in history_slice
            )
            logger.info(
                f"📜 [classify_intent] Historial (últimos {len(history_slice)} msgs, "
                f"tiene_productos_en_ctx={has_product_ctx}): "
                + " | ".join(
                    f"[{m.__class__.__name__[:2]}] {getattr(m, 'content', '')[:120]!r}"
                    for m in history_slice
                )
            )
            if not has_product_ctx:
                logger.warning(
                    "⚠️ [classify_intent] Historial SIN [CONTEXTO DEL SISTEMA] — "
                    "PRODUCT_DETAIL no podrá detectarse aunque el usuario pida detalles"
                )

        _print_llm_payload("classify_intent (PASO 3)", classify_messages)
        t0 = time.time()
        classification = _llm.invoke(classify_messages)
        elapsed = time.time() - t0
        intent_raw = classification.content.strip().upper()
        resp_chars = len(classification.content)
        logger.info(
            f"⏱️ [classify_intent] LLM respondió en {elapsed:.2f}s | "
            f"respuesta={resp_chars}c: '{classification.content.strip()}'"
        )

        if "AGENT_DIRECT" in intent_raw:
            return "AGENT_DIRECT"
        elif "AGENT" in intent_raw:
            return "AGENT"
        elif "BALANCE" in intent_raw:
            return "BALANCE"
        elif "PAYMENT" in intent_raw:
            return "PAYMENT"
        elif "GREETING" in intent_raw:
            return "GREETING"
        elif "PRODUCT_DETAIL" in intent_raw:
            return "PRODUCT_DETAIL"
        elif "PRODUCT" in intent_raw:
            return "PRODUCT"
        else:
            return "OTHER"

    async def search_products_mcp(self, query: str, count: int) -> dict:
        """
        Llama al tool search_products via MCP.
        Retorna dict con {products: [], totalFound: N, query: str}.
        """
        try:
            tools = get_tools()
            search_tool = next((t for t in tools if t.name == "search_products"), None)

            if not search_tool:
                logger.error("❌ Tool search_products no encontrado en MCP")
                return {"products": [], "totalFound": 0}

            result_raw = search_tool.invoke({"query": query, "count": count})
            result_data = _extract_mcp_result(result_raw)

            products = []
            if isinstance(result_data, list):
                products = result_data
            elif isinstance(result_data, dict):
                products = result_data.get("products", [])

            total_found = len(products)
            if isinstance(result_data, dict):
                total_found = result_data.get("totalFound", len(products))

            return {"products": products, "totalFound": total_found, "query": query}

        except Exception as e:
            logger.error(f"❌ Error invocando search_products MCP: {e}")
            return {"products": [], "totalFound": 0}

    async def get_product_detail_mcp(self, product_name: str, product_id: str | None = None) -> dict:
        """
        Llama al tool search_product_detail via MCP.
        Intenta primero por nombre y luego por ID como fallback.
        """
        try:
            tools = get_tools()
            detail_tool = next((t for t in tools if t.name == "search_product_detail"), None)

            if not detail_tool:
                logger.error("❌ Tool search_product_detail no encontrado en MCP")
                return {}

            result_raw = detail_tool.invoke({"productName": product_name})
            result_data = _extract_mcp_result(result_raw)

            if isinstance(result_data, dict) and result_data:
                return result_data

            if product_id:
                logger.info(f"🔄 Reintentando búsqueda de detalle con productId: {product_id}")
                result_raw = detail_tool.invoke({"productName": product_id})
                result_data = _extract_mcp_result(result_raw)
                if isinstance(result_data, dict) and result_data:
                    return result_data

            return {}

        except Exception as e:
            logger.error(f"❌ Error invocando search_product_detail MCP: {e}")
            return {}

    def extract_product_name_from_history(
        self,
        question: str,
        history_messages: list,
    ) -> tuple[str | None, str | None]:
        """
        Usa el LLM para identificar a qué producto del historial se refiere el usuario.
        Retorna (nombre_producto, product_id) o (None, None).
        """
        try:
            extract_messages = [SystemMessage(content=SEARCH_EXTRACT_PRODUCT_NAME_PROMPT)]
            if history_messages:
                extract_messages.extend(history_messages[-10:])
            extract_messages.append(HumanMessage(content=question))

            response = _llm.invoke(extract_messages)
            result = response.content.strip()

            # Pass through comparison format unchanged
            if result.upper().startswith("COMPARAR:"):
                return result, None

            if result == "NO_MATCH" or not result:
                return None, None

            if "|" in result:
                parts = result.split("|", 1)
                product_name = parts[0].strip()
                product_id = parts[1].strip() if len(parts) > 1 else None
                return product_name, product_id

            return result, None

        except Exception as e:
            logger.error(f"❌ Error extrayendo nombre de producto del historial: {e}")
            return None, None

    def parse_products(self, raw_products: list) -> list[SearchProductItem]:
        """Convierte la lista de productos de la API/MCP a modelos SearchProductItem."""
        items = []
        for p in raw_products:
            items.append(SearchProductItem(
                productId=p.get("productId"),
                skuId=p.get("skuId"),
                name=p.get("name"),
                brand=p.get("brand"),
                category=p.get("category"),
                link=p.get("link"),
                price=str(p.get("spotPrice") or p.get("price")) if (p.get("spotPrice") or p.get("price")) is not None else None,
                image=p.get("image"),
                specs=p.get("specs"),
            ))
        return items

    def generate_search_response(
        self,
        question: str,
        extracted_query: str,
        raw_products: list,
        total_found: int,
        history_messages: list,
        is_first_interaction: bool,
    ) -> str:
        """
        Genera la respuesta conversacional para una búsqueda de productos usando LLM.
        Incluye emojis y adapta el tono según si es primera interacción o no.
        """
        if is_first_interaction:
            greeting_hint = "Es la PRIMERA interacción. Saluda brevemente (ej: '¡Hola!') y luego presenta los productos. No hagas una presentación larga."
            history_hint = ""
        else:
            greeting_hint = ""
            history_hint = "IMPORTANTE: Ya hay conversación previa. NO saludes ni digas 'Hola'. Ve directo a los productos."

        if raw_products:
            products_context = "\n".join([
                f"- {p.get('name', 'Sin nombre')} | Precio: ${p.get('spotPrice') or p.get('price', 'N/A')} | Marca: {p.get('brand', 'N/A')}"
                for p in raw_products
            ])
            system_prompt = _SEARCH_RESPONSE_PROMPT_WITH_EMOJIS.format(
                marci_identity=MARCI_BASE_IDENTITY,
                greeting_hint=greeting_hint,
                history_hint=history_hint,
                query=extracted_query,
                total=total_found,
                products_context=products_context,
            )
        else:
            system_prompt = SEARCH_NO_RESULTS_PROMPT.format(
                marci_identity=MARCI_BASE_IDENTITY,
                greeting_hint=greeting_hint,
                history_hint=history_hint,
                query=extracted_query,
            )

        llm_messages = [SystemMessage(content=system_prompt)]
        recent_history = history_messages[-10:] if len(history_messages) > 10 else history_messages
        llm_messages.extend(recent_history)
        llm_messages.append(HumanMessage(content=question))

        sys_chars = len(system_prompt)
        hist_chars = sum(len(getattr(m, "content", "")) for m in recent_history)
        query_chars = len(question)
        total_chars = sys_chars + hist_chars + query_chars
        logger.info(
            f"📤 [generate_search_response] Enviando al LLM | "
            f"sys_prompt={sys_chars}c (~{sys_chars//4}tok) | "
            f"historial={len(recent_history)} msgs/{hist_chars}c | "
            f"question={query_chars}c | "
            f"productos_en_prompt={total_found} | "
            f"TOTAL ~{total_chars//4} tokens estimados"
        )
        logger.info(f"📝 [generate_search_response] Pregunta: '{question}' | Query extraído: '{extracted_query}'")
        logger.info(
            f"📋 [generate_search_response] System prompt (primeros 400c):\n"
            f"{system_prompt[:400]}{'...' if len(system_prompt) > 400 else ''}"
        )

        _print_llm_payload("generate_search_response (PASO 8)", llm_messages)
        t0 = time.time()
        response = _llm.invoke(llm_messages)
        elapsed = time.time() - t0
        answer = response.content
        logger.info(
            f"⏱️ [generate_search_response] LLM respondió en {elapsed:.2f}s | "
            f"respuesta={len(answer)}c: '{answer[:120]}...'"
        )

        if "[CONTEXTO DEL SISTEMA" in answer:
            answer = answer.split("[CONTEXTO DEL SISTEMA")[0].strip()
            logger.warning("Se eliminó texto '[CONTEXTO DEL SISTEMA...]' copiado por el LLM")

        return answer

    def generate_no_promo_available_response(
        self,
        question: str,
        extracted_query: str,
        raw_products: list,
        history_messages: list,
        is_first_interaction: bool,
    ) -> str:
        """
        Genera respuesta cuando el usuario pidió X en promoción pero ningún producto
        disponible tiene promotion=True. Se informa al usuario y se muestran alternativas.
        """
        if is_first_interaction:
            greeting_hint = "Es la PRIMERA interacción. Saluda brevemente (ej: '¡Hola!') y luego presenta los productos."
            history_hint = ""
        else:
            greeting_hint = ""
            history_hint = "IMPORTANTE: Ya hay conversación previa. NO saludes ni digas 'Hola'. Ve directo."

        products_context = "\n".join([
            f"- {p.get('name', 'Sin nombre')} | Precio: ${p.get('spotPrice') or p.get('price', 'N/A')} | Marca: {p.get('brand', 'N/A')}"
            for p in raw_products
        ]) if raw_products else "Sin productos disponibles."

        system_prompt = SEARCH_NO_PROMO_AVAILABLE_PROMPT.format(
            marci_identity=MARCI_BASE_IDENTITY,
            greeting_hint=greeting_hint,
            history_hint=history_hint,
            query=extracted_query,
            products_context=products_context,
        )

        llm_messages = [SystemMessage(content=system_prompt)]
        recent_history = history_messages[-10:] if len(history_messages) > 10 else history_messages
        llm_messages.extend(recent_history)
        llm_messages.append(HumanMessage(content=question))

        t0 = time.time()
        response = _llm.invoke(llm_messages)
        elapsed = time.time() - t0
        answer = response.content
        logger.info(
            f"⏱️ [generate_no_promo_available_response] LLM respondió en {elapsed:.2f}s | "
            f"respuesta={len(answer)}c: '{answer[:120]}...'"
        )
        if "[CONTEXTO DEL SISTEMA" in answer:
            answer = answer.split("[CONTEXTO DEL SISTEMA")[0].strip()
        return answer

    def generate_detail_response(
        self,
        question: str,
        product: SearchProductItem | None,
        detail_data: dict,
    ) -> str:
        """Genera la respuesta conversacional para el detalle de un producto usando LLM."""
        product_json = json_mod.dumps(
            product.model_dump(exclude_none=True) if product else detail_data,
            ensure_ascii=False,
            indent=2,
        )
        system_prompt = SEARCH_PRODUCT_DETAIL_PROMPT.format(
            marci_identity=MARCI_BASE_IDENTITY,
            product_detail=product_json,
        )
        llm_messages = [SystemMessage(content=system_prompt)]
        llm_messages.append(HumanMessage(content=question))

        response = _llm.invoke(llm_messages)
        answer = response.content.strip()

        if "[CONTEXTO DEL SISTEMA" in answer:
            answer = answer.split("[CONTEXTO DEL SISTEMA")[0].strip()

        return answer

    def build_history_content(
        self,
        answer: str,
        products: list[SearchProductItem],
        is_detail: bool = False,
    ) -> str:
        """
        Construye el contenido del historial anotando los productos mostrados al usuario.
        Esto permite al LLM recordar qué productos se presentaron en turnos anteriores.
        """
        history_content = answer
        if products:
            if is_detail:
                p = products[0]
                history_content += "\n\n[CONTEXTO DEL SISTEMA - DETALLE DE PRODUCTO]:\n"
                history_content += f"- {p.name} (ID: {p.productId}) - Precio: ${p.price} - Categoría: {p.category or 'N/A'}\n"
            else:
                product_details = "\n\n[CONTEXTO DEL SISTEMA - PRODUCTOS MOSTRADOS AL USUARIO]:\n"
                for p in products:
                    product_details += f"- {p.name} (ID: {p.productId}) - Precio: ${p.price} - Categoría: {p.category or 'N/A'}\n"
                history_content += product_details
        return history_content

    def extract_product_query(self, question: str, history_messages: list) -> str:
        """
        Extrae el término de búsqueda del mensaje del usuario usando LLM.
        Retorna "NO_PRODUCT" si no se detecta producto en el mensaje.
        """
        extract_messages = [SystemMessage(content=SEARCH_EXTRACT_QUERY_PROMPT)]
        history_slice = history_messages[-CLASSIFY_HISTORY_SLICE:] if history_messages else []
        if history_slice:
            extract_messages.extend(history_slice)
        extract_messages.append(HumanMessage(content=question))

        sys_chars = len(SEARCH_EXTRACT_QUERY_PROMPT)
        hist_chars = sum(len(getattr(m, "content", "")) for m in history_slice)
        query_chars = len(question)
        total_chars = sys_chars + hist_chars + query_chars
        logger.info(
            f"📤 [extract_product_query] Enviando al LLM | "
            f"sys_prompt={sys_chars}c (~{sys_chars//4}tok) | "
            f"historial={len(history_slice)} msgs/{hist_chars}c | "
            f"question={query_chars}c | "
            f"TOTAL ~{total_chars//4} tokens estimados"
        )
        logger.info(f"📝 [extract_product_query] Pregunta del usuario: '{question}'")
        if history_slice:
            logger.info(
                f"📜 [extract_product_query] Historial (últimos {len(history_slice)} msgs): "
                + " | ".join(
                    f"[{m.__class__.__name__[:2]}] {getattr(m, 'content', '')[:80]!r}"
                    for m in history_slice
                )
            )

        _print_llm_payload("extract_product_query (PASO 5)", extract_messages)
        t0 = time.time()
        response = _llm.invoke(extract_messages)
        elapsed = time.time() - t0
        result = response.content.strip()
        resp_chars = len(response.content)
        logger.info(
            f"⏱️ [extract_product_query] LLM respondió en {elapsed:.2f}s | "
            f"respuesta={resp_chars}c: '{result}'"
        )
        return result

    def generate_no_product_response(self, question: str, history_messages: list) -> str:
        """Genera una respuesta contextual cuando el usuario no menciona ningún producto."""
        no_prod_messages = [SystemMessage(content=SEARCH_NO_PRODUCT_PROMPT)]
        no_prod_messages.extend(history_messages[-6:])
        no_prod_messages.append(HumanMessage(content=question))
        response = _llm.invoke(no_prod_messages)
        answer = response.content.strip()
        if "[CONTEXTO DEL SISTEMA" in answer:
            answer = answer.split("[CONTEXTO DEL SISTEMA")[0].strip()
        return answer

    def generate_greeting_response(self, question: str, history_messages: list) -> str:
        """Genera un saludo conversacional cuando el usuario ya tiene historial previo."""
        messages = [SystemMessage(content=GREETING_FOLLOWUP_PROMPT.format(marci_identity=MARCI_BASE_IDENTITY))]
        messages.extend(history_messages[-CLASSIFY_HISTORY_SLICE:])
        messages.append(HumanMessage(content=question))
        response = _llm.invoke(messages)
        answer = response.content.strip()
        if "[CONTEXTO DEL SISTEMA" in answer:
            answer = answer.split("[CONTEXTO DEL SISTEMA")[0].strip()
        return answer

    def generate_compare_response(
        self,
        question: str,
        product1_data: dict,
        product2_data: dict,
    ) -> str:
        """Genera la respuesta de comparativa entre dos productos usando LLM."""
        product1_json = json_mod.dumps(product1_data, ensure_ascii=False, indent=2)
        product2_json = json_mod.dumps(product2_data, ensure_ascii=False, indent=2)
        system_prompt = SEARCH_PRODUCT_COMPARE_PROMPT.format(
            marci_identity=MARCI_BASE_IDENTITY,
            product1_detail=product1_json,
            product2_detail=product2_json,
        )
        llm_messages = [SystemMessage(content=system_prompt)]
        llm_messages.append(HumanMessage(content=question))

        logger.info(
            f"📤 [generate_compare_response] Comparando 2 productos | "
            f"sys_prompt={len(system_prompt)}c (~{len(system_prompt)//4}tok)"
        )
        t0 = time.time()
        response = _llm.invoke(llm_messages)
        elapsed = time.time() - t0
        answer = response.content.strip()
        logger.info(f"⏱️ [generate_compare_response] LLM respondió en {elapsed:.2f}s")

        if "[CONTEXTO DEL SISTEMA" in answer:
            answer = answer.split("[CONTEXTO DEL SISTEMA")[0].strip()

        return answer

    def generate_resume_purchase_greeting(self, products_text: str) -> str:
        """
        Genera un saludo con recomendación de los últimos productos vistos por el usuario.
        Se usa cuando la conversación es nueva y el usuario saluda o pide recomendaciones.
        products_text: string ya formateado con los nombres de los productos.
        """
        system_prompt = RESUME_PURCHASE_GREETING_PROMPT.format(
            marci_identity=MARCI_BASE_IDENTITY,
            products_context=products_text,
        )
        llm_messages = [SystemMessage(content=system_prompt)]
        logger.info(f"🛒 [generate_resume_purchase_greeting] Generando saludo con productos: {products_text[:100]}")
        response = _llm.invoke(llm_messages)
        return response.content.strip()
