import json
import datetime
import pytz
import re
import time
from typing import Literal

from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, AIMessage
from langgraph.graph import StateGraph, END

from .state import AgentState
from .prompts import SYSTEM_PROMPT, get_classification_prompt
from .skills.ub_presupuesto_skill import ub_presupuesto_skill_node
from .skills.cartera_skill import cartera_skill_node
from .skills.talento_humano_skill import talento_humano_skill_node
from .skills.resumen_pdf_skill import resumen_pdf_skill_node
from .utils.precalc_helper import get_active_archetypes, get_cached_result, save_archetype_to_backend
from config.settings import settings

# Inicializar LLM
llm = ChatOpenAI(
    model=settings.MODEL_NAME, 
    temperature=0,
    api_key=settings.OPENAI_API_KEY
)

def extract_usage(message):
    """Extrae el consumo de tokens."""
    usage = message.response_metadata.get("token_usage", {})
    return {
        "prompt": usage.get("prompt_tokens", 0),
        "completion": usage.get("completion_tokens", 0),
        "total": usage.get("total_tokens", 0)
    }


async def security_check_node(state: AgentState):
    """Verifica si la sesión de 12 horas ha caducado y solicita la cédula si es necesario."""
    messages = state.get("messages", [])
    if not messages:
        return {"is_authenticating": False}
    
    last_msg = messages[-1].content
    now = time.time()
    last_val = state.get("auth_validated_at")
    is_auth = state.get("is_authenticating", False)
    
    # 1. ¿Sesión válida? (12 horas = 43200 segundos)
    if last_val and (now - last_val < 43200):
        return {"is_authenticating": False} # Sesión vigente
        
    # 2. Si estamos en proceso de autenticación, validar el input
    if is_auth:
        # Validar formato de cédula (10 dígitos en Ecuador)
        cedula_match = re.match(r'^\d{10}$', last_msg.strip())
        if cedula_match:
            print("🔒 [SECURITY] Cédula validada. Sesión iniciada por 12h.")
            return {
                "auth_validated_at": now,
                "is_authenticating": False,
                "intencion": "security",
                "user_cedula": cedula_match.group(0),
                "messages": [AIMessage(content="✅ Acceso validado correctamente. ¿En qué puedo ayudarte hoy?")]
            }
        else:
            return {
                "is_authenticating": True,
                "messages": [AIMessage(content="⚠️ El número de cédula ingresado no es válido (debe tener 10 dígitos). Por favor, intenta de nuevo.")]
            }

    # 3. Si no hay sesión o expiró, pedir la cédula
    print("🔒 [SECURITY] Sesión expirada o inexistente. Solicitando cédula.")
    return {
        "is_authenticating": True,
        "messages": [AIMessage(content="Hola, por motivos de seguridad tu sesión ha expirado (12h). Por favor, ingresa tu número de cédula para continuar.")]
    }

def security_router(state: AgentState) -> Literal["classify", "END"]:
    """Decide si continuar con el flujo o detenerse a esperar la cédula."""
    if state.get("is_authenticating") or not state.get("auth_validated_at"):
        return "END"
    return "classify"

async def classify_intent_node(state: AgentState):
    """Nodo para clasificar la intención y detectar arquetipos pre-calculados."""
    messages = state["messages"]
    if not messages:
        return {"intencion": "GENERAL_CHAT", "archetype_key": None}

    # Obtener lista de arquetipos activos de Java
    archetypes = await get_active_archetypes()
    archetypes_txt = "\n".join([f"- {a['archetypeKey']}: {a['sampleQuestion']}" for a in archetypes]) if archetypes else "Ninguno disponible"

    recent_messages = messages[-5:]
    response = await llm.ainvoke([
        SystemMessage(content=get_classification_prompt(archetypes_txt)),
        *recent_messages
    ])
    
    try:
        content = response.content.strip()
        json_match = re.search(r"\{.*\}", content, re.DOTALL)
        data = json.loads(json_match.group(0)) if json_match else {}
        
        intenciones = list(set(data.get("intenciones", []))) # Asegurar valores únicos
        # Caso especial: Si solo viene "intencion" (legacy), convertir a lista
        if not intenciones and data.get("intencion"):
            intenciones = [data.get("intencion")]
            
        entities = data.get("entities", {})
        
        return {
            "intenciones": intenciones,
            "intenciones_pendientes": intenciones.copy(), # Cola de ejecución secuencial
            "intencion": intenciones[0] if intenciones else "UNKNOWN", # Primer objetivo
            "archetype_key": data.get("archetype_key"),
            "last_entities": entities,
            "user_name": entities.get("user_name") or state.get("user_name"),
            "user_position": entities.get("user_position") or state.get("user_position"),
            "usage": extract_usage(response),
            "is_precalculated": False,
            "resultados_tecnicos": [], # LIMPIEZA CRÍTICA: nuevas consultas no heredan basura visual del reporte
            "mensaje_skill": "",
            "dashboard": "",
            "dax_query": "",
            "interactive_data": None # LIMPIEZA CRÍTICA AL INICIO: Al procesar un nuevo mensaje, borramos el PDF del turno anterior
        }
    except Exception as e:
        print(f"⚠️ Error clasificación: {e}")
        return {"intencion": "UNKNOWN", "archetype_key": None, "usage": extract_usage(response)}

async def check_cache_node(state: AgentState):
    """Verifica si existe un resultado pre-calculado en Java para el arquetipo detectado."""
    archetype_key = state.get("archetype_key")
    if not archetype_key:
        return {"is_precalculated": False}

    print(f"🔍 [CACHE] Buscando resultado para arquetipo: {archetype_key}")
    cache_result = await get_cached_result(archetype_key)
    
    if cache_result:
        print("✅ [CACHE] Hit! Usando resultado pre-calculado de Java.")
        # Procesar el resultado (asumimos formato PowerBIQueryResponse)
        # PBI response structure: { "results": [{ "tables": [{ "rows": [...] }] }] }
        return {
            "ultimo_resultado": json.dumps(cache_result, ensure_ascii=False),
            "is_precalculated": True,
            "intencion": state.get("intencion") # Mantener la intención
        }
    
    print("❌ [CACHE] Miss. Procediendo a consulta en vivo.")
    return {"is_precalculated": False}

async def register_archetype_node(state: AgentState):
    """Nodo que registra automáticamente consultas exitosas como nuevos arquetipos en el backend."""
    # Solo registrar si no es pre-calculado (fue una consulta en vivo) y tenemos un resultado útil
    if not state.get("is_precalculated") and state.get("ultimo_resultado") and state.get("dax_query"):
        
        # 🚨 FILTRO CRÍTICO: No registrar si es una respuesta de chat general, error o seguridad
        allowed_intents = ["UB_PRESUPUESTO", "CARTERA", "TALENTO_HUMANO"]
        if state.get("intencion") not in allowed_intents:
            return {}
            
        # No registrar si el DAX contiene respuestas conversacionales (alucinaciones)
        if "¡HOLA" in state.get("dax_query", "").upper():
            return {}

        question = state["messages"][-1].content
        dax = state.get("dax_query")
        category = state.get("intencion")
        
        print(f"🧠 [LEARNING] Auto-registrando nueva consulta como arquetipo: {question[:50]}...")
        await save_archetype_to_backend(question, dax, category)
        
    return {}

async def response_node(state: AgentState):
    """Genera la respuesta final persuasiva y ejecutiva usando Felizia."""
    messages = state["messages"]
    tz = pytz.timezone("America/Guayaquil")
    current_date = datetime.datetime.now(tz).strftime("%Y-%m-%d")
    sys_prompt = SYSTEM_PROMPT.format(current_date=current_date)
    
    context = f"Intención Detectada: {state.get('intencion')}\n"
    if state.get("user_name"): 
        context += f"Usuario: {state.get('user_name')}\n"
    if state.get("user_position"):
        context += f"Cargo: {state.get('user_position')}\n"
    
    # El Relieve técnico es la pieza clave para que Felizia reporte los datos exactos del MCP
    resultados = state.get("resultados_tecnicos", [])
    if resultados:
        context += "Datos Técnicos Consolidados (MCP):\n"
        for i, res in enumerate(resultados):
            context += f"--- Resultado {i+1} ---\n{res}\n"
    elif state.get("ultimo_resultado"):
        context += f"Dato Técnico (MCP): {state.get('ultimo_resultado')}"

    response_obj = await llm.ainvoke([
        SystemMessage(content=sys_prompt),
        *(messages[-10:]),
        SystemMessage(content=f"Contexto técnico actual:\n{context}")
    ])

    return {
        "messages": [AIMessage(content=response_obj.content)],
        "usage": extract_usage(response_obj)
        # Se ELIMINA la limpieza aquí para que la API detecte el PDF de este turno
    }

# --- Lógica de Enrutamiento ---
def unified_router(state: AgentState) -> Literal["ub_presupuesto_skill", "cartera_skill", "talento_humano_skill", "resumen_pdf_skill", "response_node"]:
    """Router secuencial que maneja la cola de intenciones (Dashboards)."""
    
    # 1. Prioridad: Caché de Java
    if state.get("is_precalculated"):
        return "response_node"
    
    # 2. Cola de ejecución secuencial
    pendientes = state.get("intenciones_pendientes", [])
    
    if not pendientes:
        print("🏁 [ROUTER] No hay más tareas pendientes. Yendo a respuesta final.")
        return "response_node"

    # Tomamos la siguiente intención de la cola
    proxima = pendientes[0]
    print(f"🔄 [ROUTER] Tareas pendientes: {pendientes}. Ejecutando: {proxima}")

    if proxima == "UB_PRESUPUESTO":
        return "ub_presupuesto_skill"
    elif proxima == "CARTERA":
        return "cartera_skill"
    elif proxima == "TALENTO_HUMANO":
        return "talento_humano_skill"
    elif proxima == "RESUMEN_EJECUTIVO_PDF":
        return "resumen_pdf_skill"
    
    return "response_node"

# --- Definición del Grafo ---
workflow_builder = StateGraph(AgentState)

workflow_builder.add_node("security_check", security_check_node)
workflow_builder.add_node("classify", classify_intent_node)
workflow_builder.add_node("check_cache", check_cache_node)
workflow_builder.add_node("ub_presupuesto_skill", ub_presupuesto_skill_node)
workflow_builder.add_node("cartera_skill", cartera_skill_node)
workflow_builder.add_node("talento_humano_skill", talento_humano_skill_node)
workflow_builder.add_node("resumen_pdf_skill", resumen_pdf_skill_node)
workflow_builder.add_node("register_archetype", register_archetype_node)
workflow_builder.add_node("response_node", response_node)

workflow_builder.set_entry_point("security_check")

# Filtro de Seguridad
workflow_builder.add_conditional_edges("security_check", security_router, {
    "classify": "classify",
    "END": END
})

# Flujo Lineal: Clasificar -> Verificar Caché -> Decidir camino
workflow_builder.add_edge("classify", "check_cache")

# El router unificado decide desde check_cache y desde cada skill finalizada (Bucle de cola)
destinos_router = {
    "ub_presupuesto_skill": "ub_presupuesto_skill",
    "cartera_skill": "cartera_skill",
    "talento_humano_skill": "talento_humano_skill",
    "resumen_pdf_skill": "resumen_pdf_skill",
    "response_node": "response_node"
}

workflow_builder.add_conditional_edges("check_cache", unified_router, destinos_router)
workflow_builder.add_conditional_edges("ub_presupuesto_skill", unified_router, destinos_router)
workflow_builder.add_conditional_edges("cartera_skill", unified_router, destinos_router)
workflow_builder.add_conditional_edges("talento_humano_skill", unified_router, destinos_router)
workflow_builder.add_conditional_edges("resumen_pdf_skill", unified_router, destinos_router)

workflow_builder.add_edge("response_node", END)

workflow_compiled = workflow_builder.compile()
