"""
HTTP Client with Agendex Governance.

Routes all HTTP requests through Agendex for policy evaluation.
Policies can inspect domain, path, method, and body content.
"""
from __future__ import annotations

import copy
import logging
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
from urllib.parse import urlparse

from .context import get_reasoning

if TYPE_CHECKING:
    from .client import AgendexClient

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


class GovernedHTTPSession:
    """
    HTTP session that routes all requests through Agendex governance.

    The agent never has direct API credentials - all HTTP calls go through
    Agendex proxy which holds credentials and enforces policy.

    Policies can evaluate:
    - domain/host
    - path
    - method (GET, POST, etc.)
    - body content (e.g., amount < 1000)

    Example:
        from agendex import AgendexClient
        from agendex.http import GovernedHTTPSession

        client = AgendexClient()
        http = GovernedHTTPSession(client, task="vendor_payment")

        # This request will be governed - policy checks body.amount
        result = http.post(
            "https://payments.api/v1/pay",
            json={"amount": 500, "vendor": "ACME"}
        )

        # With reasoning for audit trail
        result = http.post(
            "https://payments.api/v1/pay",
            json={"amount": 500, "vendor": "ACME"},
            reasoning="Processing approved vendor invoice #12345"
        )

        # Task switching (returns clone, safe for concurrent use)
        with http.with_task("emergency_payment") as http_emergency:
            http_emergency.post(...)
    """

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

        Args:
            agendex: The AgendexClient instance
            task: Task context for all requests (used for policy scoping)
            action: Override action name (default: from config or "http.service.call")
            on_event: Optional callback for governance events
            default_context: Default context merged into all operations (e.g., user_prompt)
        """
        self.agendex = agendex
        self._task = task
        self._action = action
        self.on_event = on_event
        self.default_context = default_context or {}

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

    @property
    def action(self) -> str:
        """
        Get the action name (custom → global config → default).
        """
        if self._action:
            return self._action
        from .config import get_global_config
        return get_global_config().get_action("http.request", "http.service.call")

    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 client with the new task.
        Safe for concurrent/async usage - original is never mutated.

        Example:
            with http.with_task("emergency_payment") as http_emergency:
                http_emergency.post(...)
            # Original http still has original task
        """
        return _TaskContext(self, task)

    def request(
        self,
        method: str,
        url: str,
        json: Optional[Dict[str, Any]] = None,
        data: Optional[Any] = None,
        headers: Optional[Dict[str, str]] = None,
        context: Optional[Dict[str, Any]] = None,
        reasoning: Optional[str] = None,
        **kwargs: Any,
    ) -> Dict[str, Any]:
        """
        Make an HTTP request through Agendex governance.

        Args:
            method: HTTP method (GET, POST, PUT, DELETE, etc.)
            url: Target URL
            json: JSON body (will be inspected by policy)
            data: Form data
            headers: HTTP headers
            context: Optional additional context for governance
            reasoning: Optional reasoning/explanation for audit trail
                       (auto-captured from contextvar if not provided)
            **kwargs: Additional arguments (ignored in governed mode)

        Returns:
            Response data from HTTP adapter

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

        # Parse URL for policy evaluation
        parsed = urlparse(url)

        # Determine service name from host
        host = parsed.netloc
        service = host.split(".")[0] if host else "unknown"

        # Build params for Agendex evaluation
        params = {
            "method": method.upper(),
            "url": url,
            "service": service,
            "host": host,
            "path": parsed.path,
            "body": json or data,
        }

        # Build resource descriptor for policy matching
        resources = [{
            "type": "http",
            "service": service,
            "method": method.upper(),
            "path": parsed.path,
            "host": host,
        }]

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

        # Invoke through Agendex
        result = self.agendex.invoke(
            action=self.action,
            params=params,
            task=self._task,
            context=merged_context if merged_context else None,
            resources=resources,
            on_event=self.on_event,
        )

        return result

    def get(self, url: str, **kwargs: Any) -> Dict[str, Any]:
        """HTTP GET request."""
        return self.request("GET", url, **kwargs)

    def post(self, url: str, **kwargs: Any) -> Dict[str, Any]:
        """HTTP POST request."""
        return self.request("POST", url, **kwargs)

    def put(self, url: str, **kwargs: Any) -> Dict[str, Any]:
        """HTTP PUT request."""
        return self.request("PUT", url, **kwargs)

    def patch(self, url: str, **kwargs: Any) -> Dict[str, Any]:
        """HTTP PATCH request."""
        return self.request("PATCH", url, **kwargs)

    def delete(self, url: str, **kwargs: Any) -> Dict[str, Any]:
        """HTTP DELETE request."""
        return self.request("DELETE", url, **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, client: GovernedHTTPSession, task: str):
        self.client = client
        self.new_task = task
        self.clone: Optional[GovernedHTTPSession] = None

    def __enter__(self) -> GovernedHTTPSession:
        self.clone = copy.copy(self.client)
        self.clone._task = self.new_task
        return self.clone

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