import os
from pathlib import Path
from typing import Dict, Any

from dotenv import load_dotenv
import json
import re
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.memory import ConversationBufferMemory
from langchain_mcp_adapters.client import MultiServerMCPClient 
from fastapi import FastAPI, HTTPException, APIRouter 
import uvicorn
from pydantic import BaseModel

# ———————————————————————————————————————————
# 0️⃣ Constantes y Configuración
# ———————————————————————————————————————————
BASE_DIR = Path(__file__).resolve().parent
load_dotenv(BASE_DIR / ".env", override=True)

# URL del MCP de Transporte
MCP_URL = os.getenv("MCP_HTTP_URL")
openai_key = os.getenv("OPENAI_API_KEY")

# Variables globales para el agente y la memoria 
AGENT_EXECUTOR: AgentExecutor | None = None
CONVERSATION_MEMORIES: Dict[str, ConversationBufferMemory] = {}
# Etiqueta para la detección de órdenes, definida como constante
NOTIFICATION_TAG = "[ORDEN_CREADA_EXITOSAMENTE]" 

# Esquema para el cuerpo del request del webhook de entrada
class UserMessage(BaseModel):
    whatsapp_id: str
    mensaje: str

# --- CONFIGURACIÓN DE FASTAPI Y CONEXIÓN A MONGO ---
app = FastAPI(title="CLIENTE MCP", root_path="/mcp-movilizia")
router = APIRouter()

# ———————————————————————————————————————————
# 1️⃣ Carga de configuración y modelo LLM
# ———————————————————————————————————————————
openai_key = os.getenv("OPENAI_API_KEY")
if not openai_key:
    # ❌ ERROR: Log al fallar la carga de la clave
    print("❌ ERROR: OPENAI_API_KEY no está definido en el archivo .env")
    raise ValueError("OPENAI_API_KEY no está definido en el archivo .env")

llm = ChatOpenAI(
    openai_api_key=openai_key,
    model="gpt-4o",
    temperature=0.2
)

# ———————————————————————————————————————————
# 2️⃣ Prompt para el Asistente LHIA (Se mantiene igual)
# ———————————————————————————————————————————
prompt = ChatPromptTemplate.from_messages([
    ("system", """
    Eres el *Asistente de Transporte Rural LHIA*, un experto en la gestión de transporte de carga agropecuaria.
    Tu objetivo es ayudar al usuario a registrarse como transportista, eliminar su registro si ya no desea trabajar,
    o crear una orden de servicio para clientes.

    [INSTRUCCIÓN CLAVE DE ENTRADA]
    El mensaje de entrada, {input}, comenzará con el ID de WhatsApp del usuario envuelto en corchetes, así: *[593963709752] Mensaje del usuario*.
    *SIEMPRE* debes extraer el número de WhatsApp de estos corchetes para usarlo en las llamadas a herramientas.

    [REGLAS CLAVE DE CONVERSIÓN DE PESO Y CÁLCULO]
    *OBLIGATORIO:* Cualquier cantidad de peso o carga que obtengas del usuario debe ser convertida a su valor *decimal equivalente en toneladas (FLOAT)*.
    - Ejemplo: "2.5 toneladas" -> 2.5
    - Ejemplo: "media tonelada" -> 0.5
    - Ejemplo: "4000 libras" -> 1.814
    - Ejemplo: "500 kilogramos" -> 0.5

    *INFERENCIA DE PESO (CLAVE PARA ÓRDENES):* Si el usuario NO proporciona un peso explícito, debes estimar el peso total en toneladas según la descripción.
    - Ejemplo: "3 vacas" -> 0.5 ton/vaca * 3 = 1.5 ton.
    - *Prioridad:* Si el usuario da un peso explícito, usa el valor proporcionado.

    [REGLAS DE OPERACIÓN CONVERSACIONAL]

    0.  *Intención de Campaña (Máxima Prioridad):*
        * *Detección:* Si el usuario usa palabras como *"campaña"* o *"campañas"*, ignora otras intenciones.
        * *Acción:* Responde con:
          "*¡Claro! Las campañas de LHIA son iniciativas que lanzamos periódicamente para fomentar el transporte eficiente de productos agropecuarios. Actualmente no hay una activa, pero te avisaremos cuando lancemos la siguiente. ¿En qué otra cosa relacionada con transporte o registro de transportistas puedo ayudarte?*"
          *NO uses herramientas.*

    1.  *Intención de Registro (Transportista - Prioridad Alta):*
        * *Detección:* Si el usuario dice frases como:
          - "quiero registrarme"
          - "quiero ser transportista"
          - "soy transportista"
          - "quiero trabajar como transportista"
          Entonces asume intención de **registro**.
        * *Flujo de Lógica (con verificación previa):*
            1. **Primero**, usa la herramienta `find_transporter_by_whatsapp` con el número extraído.
            2. **Si ya existe un transportista**, responde amablemente:
               > "Ya estás registrado como transportista. No es necesario volver a hacerlo. En cuanto tengamos nuevas órdenes disponibles, te las enviaremos directamente."
               *NO continues pidiendo datos ni registres de nuevo.*
            3. **Si NO existe el transportista**, continúa el flujo normal de registro:
                - Pide su *Nombre completo*.
                - Luego la *Placa del vehículo*.
                - Luego la *Carga Máxima* (en toneladas, pero acepta conversiones).
            4. Cuando tengas todos los datos, usa `register_new_transporter`.
               - Respuesta final al usuario:  
                 > "Bienvenido a bordo, tu registro fue exitoso. En cuanto tengamos órdenes de transporte que coincidan con tus capacidades, te las enviaremos directamente."

    1.1 *Intención de Baja (Eliminación de Registro):*
        * *Detección:* Si el usuario dice frases como:
          - "ya no quiero ser transportista"
          - "ya no quiero trabajar"
          - "ya no quiero brindar mis servicios"
          - "ya no quiero recibir órdenes"
          Entonces asume intención de **eliminar su registro**.
        * *Flujo de Lógica (Eliminación):*
            1. Usa `find_transporter_by_whatsapp` para verificar si existe.
            2. **Si existe**, usa la herramienta `delete_transporter_by_whatsapp`.
               - Luego responde con:
                 > "Tu registro ha sido eliminado correctamente. Esperamos volver a trabajar contigo en el futuro."
            3. **Si NO existe**, responde:
                 > "Aún no estás registrado como transportista. Si deseas registrarte más adelante, puedo ayudarte con eso."

    2.  *Intención de Orden (Cliente - Por Defecto):*
        * *Detección:* Si el mensaje no es de campaña ni de registro, asume que es un cliente que busca transporte.
        * *Flujo Conversacional:*
            - Si es el primer mensaje, pregunta directamente:
              > "¿Qué necesitas transportar? Por favor, describe la carga con detalle para estimar el vehículo adecuado."
            - Recolecta de forma secuencial:
              1. Nombre del cliente.
              2. Descripción de la carga.
              3. Origen (con referencia precisa).
              4. Destino.
            - No repitas preguntas si el usuario ya ha dado la información.
            - Calcula internamente el `peso_aproximado` en toneladas.
            - Cuando tengas todos los datos, usa `register_new_order`.
            - Mensaje final:
              > "Tu solicitud de transporte ha sido registrada exitosamente. Pronto un transportista se pondrá en contacto contigo."

    [REGLAS DE LOGS Y NOTIFICACIÓN]
    - *Antes de llamar a cualquier herramienta*, genera un log en el `agent_scratchpad` mostrando los parámetros exactos.
    - Para registros o eliminaciones, incluye el número de WhatsApp en el log.
    - Para órdenes, asegúrate de incluir `carga` y `peso_aproximado` correctamente.
    - En la respuesta final al usuario, **nunca muestres JSON ni etiquetas internas**.

    [RESUMEN DE TOOLS DISPONIBLES]
    - `find_transporter_by_whatsapp(whatsapp: str)` → Busca transportista por WhatsApp.
    - `delete_transporter_by_whatsapp(whatsapp: str)` → Elimina transportista por WhatsApp.
    - `register_new_transporter(transportista, vehiculo)` → Registra nuevo transportista.
    - `register_new_order(orden)` → Crea nueva orden de transporte.

    """),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])


# ———————————————————————————————————————————
# 3️⃣ Funciones de utilidad para el Asistente
# ———————————————————————————————————————————

def get_or_create_memory(whatsapp_id: str) -> ConversationBufferMemory:
    """Obtiene la memoria de conversación por 'whatsapp_id' o crea una nueva."""
    if whatsapp_id not in CONVERSATION_MEMORIES:
        print(f"✨ Creando nueva memoria para {whatsapp_id}")
        CONVERSATION_MEMORIES[whatsapp_id] = ConversationBufferMemory(
            memory_key="history",
            return_messages=True
        )
    return CONVERSATION_MEMORIES[whatsapp_id]


# ———————————————————————————————————————————
# 4️⃣ Endpoint para recibir mensajes (Webhook)
# ———————————————————————————————————————————

@router.post("/message")
async def receive_message(user_message: UserMessage):
    """
    Endpoint para recibir mensajes entrantes de un usuario de WhatsApp.
    (Implementación con Logs)
    """
    global AGENT_EXECUTOR

    # Esta verificación ahora será segura si setup_agent se ejecuta primero
    if AGENT_EXECUTOR is None:
        raise HTTPException(status_code=503, detail="El Asistente no está inicializado.")

    user_input = user_message.mensaje
    whatsapp_id = user_message.whatsapp_id

    # 🟡 LOG: Mensaje entrante
    print(f"\n🟡 LOG | Mensaje entrante")
    print(f"👤 Usuario ({whatsapp_id}) dijo: {user_input}")

    # 1. Obtener la memoria y preparar input
    memory = get_or_create_memory(whatsapp_id)
    llm_input = f"[{whatsapp_id}] {user_input}"

    try:
        AGENT_EXECUTOR.memory = memory 
        # 🟡 LOG: Invocando al agente
        print(f"🤖 Invocando al agente LHIA para: {llm_input}")
        
        result = await AGENT_EXECUTOR.ainvoke({"input": llm_input})
        assistant_response = result['output']
        
        # 🟡 LOG: Respuesta cruda del asistente
        print(f"🤖 LOG | Respuesta cruda del LLM: {assistant_response.strip()[:100]}...")

        # 2. Lógica de Detección y Extracción de JSON de Orden
        if NOTIFICATION_TAG in assistant_response:
            
            try:
                # 1. Separamos el texto después de la etiqueta
                json_and_response = assistant_response.split(NOTIFICATION_TAG, 1)[1].strip()
                
                # 2. Buscamos el primer objeto JSON completo
                json_match = re.search(r'(\{.*\})', json_and_response, re.DOTALL)
                
                if json_match:
                    json_text = json_match.group(0) # Captura el JSON completo
                    clean_response = json_and_response.replace(json_text, "").strip()
                    
                    # 🔔 NOTIFICACIÓN: Orden creada (usando get para manejo seguro de KeyError)
                    order_data = json.loads(json_text)
                    order_id = order_data.get('id_orden', order_data.get('id', 'N/A'))
                    print(f"🔔 NOTIFICACIÓN: Orden creada exitosamente para {whatsapp_id}. ID: {order_id}")
                    # 🟡 LOG: Contenido del JSON de respuesta (opcionalmente)
                    # print(f"JSON de Orden: {json_text}") 
                    
                else:
                    # ⚠️ Advertencia si el JSON no se encuentra
                    print("⚠️ Advertencia: Etiqueta de orden encontrada, pero JSON no pudo ser extraído. Enviando solo la respuesta limpia.")
                    clean_response = assistant_response.replace(NOTIFICATION_TAG, "").strip()
                    
            except json.JSONDecodeError as e:
                # ❌ ERROR: Fallo en el parsing del JSON
                print(f"❌ ERROR de JSON al parsear la respuesta del agente: {e}")
                clean_response = assistant_response.replace(NOTIFICATION_TAG, "").strip()
            except Exception as e:
                # ❌ ERROR: Error inesperado en el manejo de la notificación
                print(f"❌ ERROR inesperado al procesar la notificación de orden: {e}")
                clean_response = assistant_response.replace(NOTIFICATION_TAG, "").strip()
            
        else:
            # Lógica normal si no hay etiqueta de orden
            clean_response = assistant_response
        
        # 3. Respuesta SÍNCRONA (Webhook de entrada)
        print(f"✅ LOG | Enviando respuesta final a {whatsapp_id}. Contenido: {clean_response[:50]}...")
        return {
            "status": "success",
            "whatsapp_id": whatsapp_id,
            "mensaje_respuesta": clean_response # Retorna la respuesta limpia
        }

    except Exception as e:
        error_msg = f"❌ ERROR INTERNO: Ocurrió un error al procesar el mensaje: {e}"
        print(error_msg)
        
        raise HTTPException(
            status_code=500, 
            detail=f"Error interno del asistente al procesar la solicitud: {e}"
        )

# ———————————————————————————————————————————
# 5️⃣ Configuración inicial y Ejecución del Servidor
# ———————————————————————————————————————————

# 🔑 SOLUCIÓN: Usar un evento de startup de FastAPI para inicializar el agente
@app.on_event("startup")
async def startup_event():
    """Ejecuta setup_agent al inicio del servidor FastAPI."""
    await setup_agent()


async def setup_agent():
    """Inicializa el agente y las herramientas antes de iniciar el servidor."""
    global AGENT_EXECUTOR
    
    print(f"\n🔌 Conectando al MCP de Transporte en {MCP_URL}...")

    try:
        transporte_mcp_client = MultiServerMCPClient({
            "transporte": {
                "url": MCP_URL,
                "transport": "streamable_http"
            }
        })
        tools = await transporte_mcp_client.get_tools()
        print(f"✅ Herramientas detectadas: {[tool.name for tool in tools]}")
    except Exception as e:
        print(f"❌ ERROR: Error al conectar con el MCP: {e}")
        print("💡 Verifica que el servidor Spring AI MCP esté corriendo y accesible.")
        raise

    # El agente ahora se crea con el prompt modificado
    agent = create_openai_functions_agent(
        llm=llm,
        tools=tools,
        prompt=prompt
    )

    AGENT_EXECUTOR = AgentExecutor(
        agent=agent,
        tools=tools,
        memory=None, 
        verbose=True # Mantener verbose=True para ver el log de LangChain/Agent
    )
    print("\n🤖 Asistente de Transporte Rural LHIA listo para recibir mensajes 🚚")
    print("📢 Servidor escuchando en http://localhost:8003/mcp-movilizia/")

app.include_router(router)


if __name__ == "__main__":
    uvicorn.run(app,host="0.0.0.0", port=8003)