"""
eaigins_sdk.integrations.openai_agents
────────────────────────────────────────
Eaigins governance hook for the OpenAI Agents SDK (openai-agents).

The OpenAI Agents SDK uses a function tool pattern via @function_tool.
Eaigins wraps each tool at the function call boundary.

Install:  pip install openai-agents

Usage::

    from agents import Agent, Runner, function_tool
    from eaigins_sdk.integrations.openai_agents import governed_openai_tool
    from eaigins_sdk import ControlPlane
    from eaigins_sdk.policies import AML_POLICIES

    cp = ControlPlane(policies=AML_POLICIES)

    @function_tool
    @governed_openai_tool(cp, tool_name="file_sar",
                          context_sources=["compliance_reports"])
    def file_sar(case_id: str, customer_id: str) -> str:
        \"\"\"File a SAR with FinCEN.\"\"\"
        return submit_to_fincen(case_id, customer_id)

    agent = Agent(name="AML Agent", tools=[file_sar])
    result = Runner.run_sync(agent, "Investigate customer C-001")

eaigins_sdk.integrations.semantic_kernel
──────────────────────────────────────────
Eaigins governance hook for Microsoft Semantic Kernel.

Semantic Kernel uses KernelFunction plugins. Eaigins wraps the kernel
invocation pipeline via a KernelInvocationFilter.

Install:  pip install semantic-kernel

Usage::

    import semantic_kernel as sk
    from eaigins_sdk.integrations.semantic_kernel import EaignsKernelFilter
    from eaigins_sdk import ControlPlane
    from eaigins_sdk.policies import AML_POLICIES

    cp     = ControlPlane(policies=AML_POLICIES)
    kernel = sk.Kernel()
    kernel.add_filter(EaignsKernelFilter(cp, agent_id="sk::aml-kernel"))

    # Now every kernel function invocation passes through Eaigins
"""

from __future__ import annotations
import functools
from typing import Optional, Callable, Any

from ..models        import AgentAction, GovernanceViolationError, ExecutionDecision
from ..control_plane import ControlPlane


# ═════════════════════════════════════════════════════════════════════════════
# OpenAI Agents SDK Integration
# ═════════════════════════════════════════════════════════════════════════════

def governed_openai_tool(
    control_plane:   ControlPlane,
    tool_name:       Optional[str]       = None,
    context_sources: Optional[list[str]] = None,
    agent_id:        str                 = "openai-agents::agent",
    metadata:        Optional[dict]      = None,
) -> Callable:
    """
    Decorator for OpenAI Agents SDK @function_tool functions.

    Apply BEFORE @function_tool so Eaigins fires before the OpenAI
    agents runtime calls the function.

    Usage::

        @function_tool
        @governed_openai_tool(cp, tool_name="file_sar")
        def file_sar(case_id: str) -> str:
            ...
    """
    def decorator(func: Callable) -> Callable:
        effective_name = tool_name or func.__name__

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            action = AgentAction(
                agent_id        = agent_id,
                prompt          = f"OpenAI Agent invoking {effective_name}",
                tool_name       = effective_name,
                tool_params     = kwargs,
                context_sources = context_sources or [],
                reasoning       = f"OpenAI Agents SDK tool call: {effective_name}",
                metadata        = metadata or {"framework": "openai-agents"},
            )
            decision = control_plane.evaluate(action)
            if decision.decision == ExecutionDecision.BLOCK:
                raise GovernanceViolationError(decision)
            return func(*args, **kwargs)

        wrapper._eaigins_cp        = control_plane
        wrapper._eaigins_tool_name = effective_name
        return wrapper

    return decorator


class EaignsOpenAIAgentsHook:
    """
    OpenAI Agents SDK lifecycle hook that intercepts all tool calls.

    Usage with AgentHooks::

        from agents import Agent, Runner, AgentHooks
        from eaigins_sdk.integrations.openai_agents import EaignsOpenAIAgentsHook

        cp    = ControlPlane(policies=AML_POLICIES)
        hooks = EaignsOpenAIAgentsHook(cp, agent_id="openai::my-agent")

        agent = Agent(name="AML Agent", tools=[...], hooks=hooks)
    """

    def __init__(
        self,
        control_plane: ControlPlane,
        agent_id:      str = "openai-agents::agent",
    ):
        self.cp       = control_plane
        self.agent_id = agent_id

    async def on_tool_start(self, context: Any, tool: Any) -> None:
        """Called before each tool invocation."""
        tool_name = getattr(tool, "name", str(tool))
        params    = getattr(context, "input", {})
        if not isinstance(params, dict):
            params = {"input": str(params)[:200]}

        action = AgentAction(
            agent_id        = self.agent_id,
            prompt          = f"OpenAI Agent invoking {tool_name}",
            tool_name       = tool_name,
            tool_params     = params,
            context_sources = [],
            reasoning       = f"OpenAI Agents SDK on_tool_start: {tool_name}",
            metadata        = {"framework": "openai-agents"},
        )
        decision = self.cp.evaluate(action)
        if decision.decision == ExecutionDecision.BLOCK:
            raise GovernanceViolationError(decision)


# ═════════════════════════════════════════════════════════════════════════════
# Semantic Kernel Integration
# ═════════════════════════════════════════════════════════════════════════════

class EaignsKernelFilter:
    """
    Semantic Kernel invocation filter that intercepts all KernelFunction calls.

    Implements the IFunctionInvocationFilter interface (SK ≥ 1.0).

    Usage::

        import semantic_kernel as sk
        from eaigins_sdk.integrations.semantic_kernel import EaignsKernelFilter
        from eaigins_sdk import ControlPlane
        from eaigins_sdk.policies import HEALTHCARE_POLICIES

        cp     = ControlPlane(policies=HEALTHCARE_POLICIES)
        kernel = sk.Kernel()

        # Add Eaigins as an invocation filter
        kernel.add_filter("function_invocation", EaignsKernelFilter(cp))

        # Now ALL kernel function calls pass through Eaigins governance
    """

    def __init__(
        self,
        control_plane:   ControlPlane,
        agent_id:        str                 = "semantic-kernel::agent",
        context_sources: Optional[dict[str, list[str]]] = None,
    ):
        """
        Args:
            control_plane:   Configured ControlPlane.
            agent_id:        Agent identifier for provenance.
            context_sources: Optional mapping of function_name → [source, ...].
        """
        self.cp              = control_plane
        self.agent_id        = agent_id
        self.context_sources = context_sources or {}

    async def on_function_invocation(self, context: Any, next: Any) -> None:
        """
        Intercepts every kernel function call.
        ``context.function.name`` is the SK function name.
        ``context.arguments`` are the function arguments.
        """
        try:
            fn_name = str(getattr(
                getattr(context, "function", None), "name", "unknown_function"
            ))
            arguments = getattr(context, "arguments", {})
            params = dict(arguments) if hasattr(arguments, "__iter__") else {}

            action = AgentAction(
                agent_id        = self.agent_id,
                prompt          = f"Semantic Kernel invoking {fn_name}",
                tool_name       = fn_name,
                tool_params     = params,
                context_sources = self.context_sources.get(fn_name, []),
                reasoning       = f"SK kernel function call: {fn_name}",
                metadata        = {"framework": "semantic-kernel"},
            )
            decision = self.cp.evaluate(action)

            if decision.decision == ExecutionDecision.BLOCK:
                raise GovernanceViolationError(decision)

            # Allow execution to continue
            await next(context)

        except GovernanceViolationError:
            raise
        except Exception:
            # Don't break SK execution on unexpected errors — log and continue
            import logging
            logging.getLogger("eaigins.semantic_kernel").exception(
                "EaignsKernelFilter unexpected error"
            )
            await next(context)


def governed_sk_function(
    control_plane:   ControlPlane,
    tool_name:       Optional[str]       = None,
    context_sources: Optional[list[str]] = None,
    agent_id:        str                 = "semantic-kernel::agent",
) -> Callable:
    """
    Decorator for Semantic Kernel @kernel_function functions.

    Usage::

        from semantic_kernel.functions import kernel_function
        from eaigins_sdk.integrations.semantic_kernel import governed_sk_function

        @kernel_function(description="File a SAR report")
        @governed_sk_function(cp, tool_name="file_sar")
        def file_sar(self, case_id: str) -> str:
            return submit_to_fincen(case_id)
    """
    def decorator(func: Callable) -> Callable:
        effective_name = tool_name or func.__name__

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            action = AgentAction(
                agent_id        = agent_id,
                prompt          = f"SK kernel function: {effective_name}",
                tool_name       = effective_name,
                tool_params     = kwargs,
                context_sources = context_sources or [],
                reasoning       = f"Semantic Kernel @kernel_function call",
                metadata        = {"framework": "semantic-kernel"},
            )
            decision = control_plane.evaluate(action)
            if decision.decision == ExecutionDecision.BLOCK:
                raise GovernanceViolationError(decision)
            return func(*args, **kwargs)

        return wrapper
    return decorator
