
"""
vgovern SDK - HTTP Client

Fail-safe HTTP client for communicating with the governance backend.
All operations are non-blocking and wrapped in error handling.
"""

import json
import logging
from datetime import datetime
from typing import Any, Dict, Optional
from uuid import UUID

import requests
from requests.exceptions import Timeout, RequestException

from .sdk_types import (
    GovernanceDecision,
    GovernanceResponse,
    SDKConfig,
    StepData,
    TraceInfo,
    TraceStatus,
)
from .utils import TraceLogger, get_logger, mask_api_key


logger = get_logger(__name__)


class GovernanceClient:
    """
    HTTP client for governance backend communication.
    
    This client is designed to be fail-safe: network failures never
    block or crash the agent execution.
    """

    def __init__(self, config: SDKConfig):
        self.config = config
        self._session: Optional[requests.Session] = None
        self._logger = TraceLogger(logger)

    def _get_session(self) -> requests.Session:
        """Lazy initialization of HTTP session."""
        if self._session is None:
            self._session = requests.Session()
            self._session.headers.update({
                "Authorization": f"Bearer {self.config.api_key}",
                "Content-Type": "application/json",
                "User-Agent": f"AgentGovernanceSDK/1.0 (agent_id={self.config.agent_id})",
            })
        return self._session

    def close(self) -> None:
        """Close the HTTP session."""
        if self._session:
            self._session.close()
            self._session = None

    def _safe_log_error(self, error: Exception, context: str) -> None:
        """Log errors safely without exposing sensitive data."""
        if self.config.fail_safe_mode:
            self._logger.warning(
                f"SDK error ({context}): {type(error).__name__}. Agent continues."
            )
        else:
            self._logger.error(f"SDK error ({context}): {type(error).__name__}: {error}")

    def send_trace_start(
        self,
        trace_info: TraceInfo,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> bool:
        """
        Send trace start event to backend.
        
        Returns:
            True if successful, False otherwise (fail-safe)
        """
        payload = {
            "trace_id": str(trace_info.trace_id),
            "tenant_id": metadata.get("tenant_id") if metadata else "default",
            "agent_id": trace_info.agent_id,
            "session_id": trace_info.session_id,
            "environment": metadata.get("environment", "production") if metadata else "production",
            "trace_type": "generic",
            "initial_user_input": metadata.get("user_input") if metadata else None,
            "trigger_source": "user"
        }

        url = f"{self.config.backend_url}/v1/traces/events"
        self._logger.info(f"Sending trace start to {url} with payload: {payload}")
        return self._send_request(url, payload)

    def send_step(self, step: StepData) -> bool:
        """
        Send a step event to backend.
        
        Returns:
            True if successful, False otherwise (fail-safe)
        """
        token_usage = None
        if step.token_usage:
            token_usage = {
                "prompt": step.token_usage.prompt_tokens,
                "completion": step.token_usage.completion_tokens,
                "total": step.token_usage.total_tokens,
            }

        payload = {
            "step_id": step.step_id,
            "step_type": step.step_type.value,
            "execution_role": "agent",
            "model_name": step.model,
            "prompt": step.prompt,
            "response": step.response,
            "token_usage": token_usage,
            "latency_ms": step.latency_ms,
            "estimated_cost_usd": step.estimated_cost_usd,
        }

        url = f"{self.config.backend_url}/v1/traces/{step.trace_id}/steps"
        return self._send_request(url, payload)

    def send_trace_end(
        self,
        trace_id: UUID,
        status: TraceStatus,
        end_time: datetime,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> bool:
        """
        Send trace end event to backend.
        
        Returns:
            True if successful, False otherwise (fail-safe)
        """
        payload = {
            "end_time": end_time.isoformat(),
            "termination_reason": None,
            "terminated_by": None,
            "final_decision": "allow" if status == TraceStatus.COMPLETED else "halt",
            "total_cost_usd": 0.0,
            "total_tokens": 0,
            "tool_call_count": 0,
            "duration_ms": 0,
        }

        url = f"{self.config.backend_url}/v1/traces/{trace_id}/finalize"
        return self._send_request(url, payload)

    def check_policy(
        self,
        trace_id: UUID,
        step: StepData,
    ) -> GovernanceResponse:
        """
        Check policy for a step.
        
        Returns:
            GovernanceResponse with ALLOW or HALT decision
        """
        if not self.config.enable_policy_check:
            return GovernanceResponse(decision=GovernanceDecision.ALLOW)

        try:
            session = self._get_session()
            
            token_usage = None
            if step.token_usage:
                token_usage = {
                    "prompt_tokens": step.token_usage.prompt_tokens,
                    "completion_tokens": step.token_usage.completion_tokens,
                    "total_tokens": step.token_usage.total_tokens,
                }

            payload = {
                "trace_id": str(trace_id),
                "step_id": step.step_id,
                "step_type": step.step_type.value,
                "model": step.model,
                "token_usage": token_usage,
                "estimated_cost_usd": step.estimated_cost_usd,
                "metadata": step.metadata,
            }

            url = f"{self.config.backend_url}/v1/policies/check"
            response = session.post(url, json=payload, timeout=self.config.timeout_ms / 1000)
            response.raise_for_status()

            data = response.json()
            decision = GovernanceDecision(data.get("decision", "ALLOW"))
            return GovernanceResponse(
                decision=decision,
                reason=data.get("reason"),
                policy_id=data.get("policy_id"),
            )

        except Timeout:
            self._safe_log_error(
                Timeout("Policy check timeout"), "policy_check"
            )
            if self.config.fail_safe_mode:
                return GovernanceResponse(decision=GovernanceDecision.ALLOW)
            raise

        except RequestException as e:
            self._safe_log_error(e, "policy_check")
            if self.config.fail_safe_mode:
                return GovernanceResponse(decision=GovernanceDecision.ALLOW)
            raise

        except (json.JSONDecodeError, KeyError) as e:
            self._safe_log_error(e, "policy_check")
            if self.config.fail_safe_mode:
                return GovernanceResponse(decision=GovernanceDecision.ALLOW)
            raise

        except Exception as e:
            self._safe_log_error(e, "policy_check")
            if self.config.fail_safe_mode:
                return GovernanceResponse(decision=GovernanceDecision.ALLOW)
            raise

    def _send_request(self, url: str, payload: Dict[str, Any]) -> bool:
        """
        Send a request to the backend with fail-safe guarantees.
        
        Returns:
            True if successful, False otherwise (never raises)
        """
        try:
            session = self._get_session()
            response = session.post(url, json=payload, timeout=self.config.timeout_ms / 1000)
            response.raise_for_status()
            return True

        except Timeout as e:
            self._safe_log_error(e, "request_send")
            return False

        except RequestException as e:
            if hasattr(e, 'response') and e.response is not None:
                self._logger.warning(
                    f"RequestException: {e.response.status_code} - {e.response.text[:200]}"
                )
            self._safe_log_error(e, "request_send")
            return False

        except Exception as e:
            self._safe_log_error(e, "request_send")
            return False

    def _send_event(self, payload: Dict[str, Any]) -> bool:
        """
        Send an event to the backend with fail-safe guarantees.
        
        Returns:
            True if successful, False otherwise (never raises)
        """
        try:
            session = self._get_session()
            url = f"{self.config.backend_url}/v1/traces/events"
            response = session.post(url, json=payload, timeout=self.config.timeout_ms / 1000)
            response.raise_for_status()
            return True

        except Timeout as e:
            self._safe_log_error(e, "event_send")
            return False

        except RequestException as e:
            self._safe_log_error(e, "event_send")
            return False

        except Exception as e:
            self._safe_log_error(e, "event_send")
            return False
