"""Card helpers — create and manage KYA identity cards for LangChain agents.

Works with or without langchain installed. When langchain is available, cards
are stored on agent objects via a _kya_card attribute.

Supports:
- AgentExecutor (has .agent, .tools, .verbose)
- Runnable agents from create_react_agent / create_openai_functions_agent
- Any object with .tools attribute
"""

from __future__ import annotations

import datetime
from typing import Any, Dict, List, Optional


def _resolve_agent_type(agent: Any) -> str:
    """Determine the LangChain agent type string."""
    cls_name = type(agent).__name__

    # AgentExecutor wraps an inner agent
    if cls_name == "AgentExecutor":
        inner = getattr(agent, "agent", None)
        if inner is not None:
            inner_cls = type(inner).__name__
            return f"AgentExecutor({inner_cls})"
        return "AgentExecutor"

    # RunnableSequence / RunnableAgent from LCEL
    if "Runnable" in cls_name:
        return f"Runnable({cls_name})"

    return cls_name


def _resolve_llm_model(agent: Any) -> Optional[str]:
    """Try to extract the LLM model name from a LangChain agent."""
    # AgentExecutor -> agent -> llm
    inner = getattr(agent, "agent", agent)
    llm = getattr(inner, "llm", None)
    if llm is None:
        llm = getattr(inner, "llm_chain", None)
        if llm is not None:
            llm = getattr(llm, "llm", None)
    if llm is None:
        return None

    # ChatOpenAI, ChatAnthropic, etc. store model_name or model
    model_name = getattr(llm, "model_name", None) or getattr(llm, "model", None)
    if model_name:
        return str(model_name)
    return type(llm).__name__


def _extract_tool_capabilities(agent: Any) -> List[Dict[str, str]]:
    """Extract capabilities from a LangChain agent's tools list."""
    tools = getattr(agent, "tools", []) or []
    capabilities = []
    for tool in tools:
        name = getattr(tool, "name", None) or type(tool).__name__
        description = getattr(tool, "description", "") or ""
        capabilities.append({
            "name": name,
            "description": description[:200],
            "risk_level": "medium",
            "scope": "as-configured",
        })
    return capabilities


def _build_agent_slug(agent: Any) -> str:
    """Build a URL-safe slug from agent identity."""
    agent_type = _resolve_agent_type(agent)
    slug = agent_type.lower().replace(" ", "-").replace("_", "-")
    slug = slug.replace("(", "-").replace(")", "")
    slug = "".join(c for c in slug if c.isalnum() or c == "-")
    slug = slug.strip("-") or "agent"
    return slug


def create_agent_card(
    agent: Any,
    *,
    owner_name: str = "unspecified",
    owner_contact: str = "unspecified",
    agent_id_prefix: str = "langchain",
    capabilities: Optional[List[Dict[str, str]]] = None,
    version: str = "0.1.0",
    risk_classification: str = "minimal",
    human_oversight: str = "human-on-the-loop",
    purpose: Optional[str] = None,
) -> Dict[str, Any]:
    """Create a KYA identity card from a LangChain agent.

    Args:
        agent: A LangChain AgentExecutor, Runnable, or any object with tools.
        owner_name: Organization or person responsible for this agent.
        owner_contact: Contact email for security/compliance inquiries.
        agent_id_prefix: Prefix for the agent_id (default: "langchain").
        capabilities: Override auto-detected capabilities. If None, extracted from agent.tools.
        version: Semantic version for the agent.
        risk_classification: EU AI Act risk level (minimal/limited/high/unacceptable).
        human_oversight: Oversight level.
        purpose: Override auto-generated purpose string.

    Returns:
        A KYA card dict conforming to the v0.1 schema.
    """
    slug = _build_agent_slug(agent)
    now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")

    if capabilities is None:
        capabilities = _extract_tool_capabilities(agent)

    agent_type_str = _resolve_agent_type(agent)
    llm_model = _resolve_llm_model(agent)

    # Build purpose
    if purpose is None:
        purpose_parts = [f"LangChain {agent_type_str} agent"]
        if llm_model:
            purpose_parts.append(f"using {llm_model}")
        tool_names = [c["name"] for c in capabilities]
        if tool_names:
            purpose_parts.append(f"with tools: {', '.join(tool_names)}")
        purpose = " ".join(purpose_parts)

    # Ensure purpose meets KYA minLength of 10
    if len(purpose) < 10:
        purpose = f"LangChain agent of type {agent_type_str}"
    purpose = purpose[:500]

    # Build metadata tags
    tags = ["langchain"]
    if llm_model:
        tags.append(f"llm:{llm_model}")

    card: Dict[str, Any] = {
        "kya_version": "0.1",
        "agent_id": f"{agent_id_prefix}/{slug}",
        "name": agent_type_str,
        "version": version,
        "purpose": purpose,
        "agent_type": "autonomous",
        "owner": {
            "name": owner_name,
            "contact": owner_contact,
        },
        "capabilities": {
            "declared": capabilities,
            "denied": [],
        },
        "data_access": {
            "sources": [],
            "destinations": [],
            "pii_handling": "none",
            "retention_policy": "session-only",
        },
        "security": {
            "last_audit": None,
            "known_vulnerabilities": [],
            "injection_tested": False,
        },
        "compliance": {
            "frameworks": [],
            "risk_classification": risk_classification,
            "human_oversight": human_oversight,
        },
        "behavior": {
            "logging_enabled": False,
            "log_format": "none",
            "max_actions_per_minute": 0,
            "kill_switch": True,
            "escalation_policy": "halt-and-notify",
        },
        "metadata": {
            "created_at": now,
            "updated_at": now,
            "tags": tags,
        },
    }

    return card


def attach_card(agent: Any, card: Dict[str, Any]) -> None:
    """Attach a KYA identity card to a LangChain agent instance."""
    agent._kya_card = card


def get_card(agent: Any) -> Optional[Dict[str, Any]]:
    """Retrieve the KYA card attached to a LangChain agent, if any."""
    return getattr(agent, "_kya_card", None)
