"""
Generic Governed Tool for Agendex.

Provides a flexible wrapper for any action that needs governance,
without being tied to a specific resource type (DB, S3, HTTP, etc.).
"""
from __future__ import annotations

import copy
import logging
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

from .context import get_reasoning

if TYPE_CHECKING:
    from .client import AgendexClient

logger = logging.getLogger("agendex.tool")


class GovernedTool:
    """
    Generic wrapper for any governed action.

    Use this when you need to govern a custom action that doesn't fit
    the built-in resource clients (DB, S3, HTTP, CRM).

    Example:
        from agendex import AgendexClient, GovernedTool

        client = AgendexClient()

        # Create a governed tool for any action
        email_sender = GovernedTool(
            client,
            action="email.send",
            task="notifications",
        )

        # Simple invoke
        result = email_sender.invoke(
            to="user@example.com",
            subject="Hello",
            body="World",
        )

        # With reasoning for audit trail
        result = email_sender.invoke(
            to="user@example.com",
            subject="Order Confirmation",
            body="Your order has shipped",
            reasoning="User completed checkout, sending confirmation"
        )

        # Task switching (returns clone, safe for concurrent use)
        with email_sender.with_task("urgent_notifications") as urgent:
            urgent.invoke(to="admin@example.com", subject="Alert!")
    """

    def __init__(
        self,
        agendex: AgendexClient,
        action: str,
        task: str,
        resource_type: Optional[str] = None,
        on_event: Optional[Callable[[Dict], None]] = None,
        default_context: Optional[Dict[str, Any]] = None,
    ):
        """
        Initialize a governed tool.

        Args:
            agendex: The AgendexClient instance
            action: Action name for governance (e.g., "email.send", "slack.post")
            task: Task context for all invocations (used for policy scoping)
            resource_type: Optional resource type for auto-building resources
            on_event: Optional callback for governance events
            default_context: Default context merged into all operations
        """
        self.agendex = agendex
        self.action = action
        self._task = task
        self.resource_type = resource_type
        self.on_event = on_event
        self.default_context = default_context or {}

    @property
    def task(self) -> str:
        """Current task context."""
        return self._task

    def set_task(self, task: str) -> None:
        """
        Change the task context.

        Note: For concurrent/async code, prefer with_task() which returns
        a clone instead of mutating.
        """
        self._task = task

    def with_task(self, task: str) -> "TaskContext":
        """
        Context manager for temporary task switch.

        Returns a clone of this tool with the new task.
        Safe for concurrent/async usage - original is never mutated.

        Example:
            with tool.with_task("emergency") as emergency_tool:
                emergency_tool.invoke(...)
            # Original tool still has original task
        """
        return TaskContext(self, task)

    def invoke(
        self,
        params: Optional[Dict[str, Any]] = None,
        context: Optional[Dict[str, Any]] = None,
        reasoning: Optional[str] = None,
        resources: Optional[List[Dict[str, Any]]] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """
        Invoke the action through governance.

        Args:
            params: Explicit params dict (merged with kwargs)
            context: Optional additional context for governance
            reasoning: Optional reasoning/explanation for audit trail
                       (auto-captured from contextvar if not provided)
            resources: Optional explicit resources (if not using resource_type)
            **kwargs: Parameters as keyword arguments (merged into params)

        Returns:
            Result from the action adapter

        Raises:
            DeniedError: If action is denied by policy
            PendingApprovalError: If action requires approval
        """
        # Auto-capture reasoning from contextvar if not provided
        if reasoning is None:
            reasoning = get_reasoning()

        # Merge params: explicit dict + kwargs
        final_params = dict(params or {})
        final_params.update(kwargs)

        # Merge contexts: default → operation-specific → reasoning
        final_context = {**self.default_context, **(context or {})}
        if reasoning:
            final_context["reasoning"] = reasoning

        # Build resources if resource_type is set and no explicit resources
        final_resources = resources
        if final_resources is None and self.resource_type:
            # Only include resource type, not all params (security consideration)
            final_resources = [{"type": self.resource_type}]

        return self.agendex.invoke(
            action=self.action,
            params=final_params,
            task=self._task,
            context=final_context if final_context else None,
            resources=final_resources,
            on_event=self.on_event,
        )

    def __call__(self, **kwargs: Any) -> Dict[str, Any]:
        """Shorthand for invoke()."""
        return self.invoke(**kwargs)


class TaskContext:
    """
    Context manager for temporary task switching.

    Returns a shallow clone instead of mutating the original,
    making it safe for concurrent/async usage.
    """

    def __init__(self, tool: GovernedTool, task: str):
        self.tool = tool
        self.new_task = task
        self.clone: Optional[GovernedTool] = None

    def __enter__(self) -> GovernedTool:
        self.clone = copy.copy(self.tool)
        self.clone._task = self.new_task
        return self.clone

    def __exit__(self, *args: Any) -> None:
        self.clone = None
