"""
VasperaGuard client.

The main interface for interacting with VasperaGuard.
"""

import os
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Union
import httpx

from .models import (
    AgentContext,
    CheckResult,
    ApprovalResult,
    Policy,
    PolicyRule,
    AuditEntry,
)
from .exceptions import (
    VasperaError,
    AuthenticationError,
    RateLimitError,
    ValidationError,
    ApprovalTimeoutError,
    CommandBlockedError,
)


@dataclass
class GuardConfig:
    """Configuration for VasperaGuard client."""

    api_key: Optional[str] = None
    base_url: str = "https://vaspera-guard-api-production.up.railway.app"
    timeout: float = 30.0
    max_retries: int = 3
    agent_id: Optional[str] = None
    default_environment: Optional[str] = None
    default_policy_id: Optional[str] = None

    def __post_init__(self):
        # Try to get API key from environment if not provided
        if not self.api_key:
            self.api_key = os.environ.get("VASPERA_GUARD_API_KEY")


class Guard:
    """
    VasperaGuard client for AI agent safety.

    Usage:
        guard = Guard(api_key="vg_...")

        # Check if a command is safe
        result = guard.check("rm -rf /tmp/cache")

        if result.allowed:
            execute(command)
        elif result.requires_approval:
            approval = guard.request_approval(result.check_id, command)
            # Wait for approval...
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        base_url: Optional[str] = None,
        agent_id: Optional[str] = None,
        environment: Optional[str] = None,
        policy_id: Optional[str] = None,
        config: Optional[GuardConfig] = None,
    ):
        """
        Initialize VasperaGuard client.

        Args:
            api_key: VasperaGuard API key (or set VASPERA_GUARD_API_KEY env var)
            base_url: API base URL (default: https://api.vasperaguard.com)
            agent_id: Default agent identifier
            environment: Default environment (development, staging, production)
            policy_id: Default policy to apply
            config: Full configuration object (overrides other args)
        """
        if config:
            self.config = config
        else:
            self.config = GuardConfig(
                api_key=api_key or os.environ.get("VASPERA_GUARD_API_KEY"),
                base_url=base_url or "https://vaspera-guard-api-production.up.railway.app",
                agent_id=agent_id,
                default_environment=environment,
                default_policy_id=policy_id,
            )

        self._client = httpx.Client(
            base_url=self.config.base_url,
            timeout=self.config.timeout,
            headers=self._get_headers(),
        )

    def _get_headers(self) -> Dict[str, str]:
        headers = {
            "Content-Type": "application/json",
            "User-Agent": "vaspera-guard-python/0.1.0",
        }
        if self.config.api_key:
            headers["Authorization"] = f"Bearer {self.config.api_key}"
        return headers

    def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
        """Handle API response and raise appropriate exceptions."""
        if response.status_code == 401:
            raise AuthenticationError()
        elif response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            raise RateLimitError(retry_after=int(retry_after) if retry_after else None)
        elif response.status_code == 400:
            data = response.json()
            raise ValidationError(data.get("detail", "Validation error"))
        elif response.status_code >= 400:
            try:
                data = response.json()
                raise VasperaError(data.get("detail", "Unknown error"), response.status_code)
            except Exception:
                raise VasperaError(f"HTTP {response.status_code}", response.status_code)

        return response.json()

    def check(
        self,
        command: str,
        agent_id: Optional[str] = None,
        context: Optional[Union[AgentContext, Dict[str, Any]]] = None,
        policy_id: Optional[str] = None,
    ) -> CheckResult:
        """
        Check if a command is safe to execute.

        Args:
            command: The command to check
            agent_id: Agent identifier (uses default if not provided)
            context: Execution context (environment, project, etc.)
            policy_id: Policy to apply (uses default if not provided)

        Returns:
            CheckResult with safety analysis

        Example:
            result = guard.check("rm -rf /tmp/cache")
            if result.allowed:
                subprocess.run(command, shell=True)
        """
        # Build context
        if isinstance(context, dict):
            ctx = AgentContext(**context)
        elif context:
            ctx = context
        else:
            ctx = AgentContext(environment=self.config.default_environment)

        payload = {
            "command": command,
            "agent_id": agent_id or self.config.agent_id or "default-agent",
            "context": ctx.to_dict() if ctx else None,
            "policy_id": policy_id or self.config.default_policy_id,
        }

        response = self._client.post("/v1/check", json=payload)
        data = self._handle_response(response)
        return CheckResult.from_dict(data)

    def check_and_execute(
        self,
        command: str,
        executor: callable,
        agent_id: Optional[str] = None,
        context: Optional[Union[AgentContext, Dict[str, Any]]] = None,
        on_blocked: Optional[callable] = None,
        on_approval_required: Optional[callable] = None,
    ) -> Any:
        """
        Check a command and execute if allowed.

        Args:
            command: The command to check and execute
            executor: Function to execute the command (receives command as arg)
            agent_id: Agent identifier
            context: Execution context
            on_blocked: Callback when command is blocked
            on_approval_required: Callback when approval is required

        Returns:
            Result from executor if allowed, None otherwise

        Example:
            def run_cmd(cmd):
                return subprocess.run(cmd, shell=True, capture_output=True)

            result = guard.check_and_execute(
                "ls -la",
                executor=run_cmd,
                on_blocked=lambda r: print(f"Blocked: {r.reason}")
            )
        """
        result = self.check(command, agent_id=agent_id, context=context)

        if result.blocked:
            if on_blocked:
                on_blocked(result)
            raise CommandBlockedError(command, result.reason or "Command blocked")

        if result.requires_approval:
            if on_approval_required:
                on_approval_required(result)
            return None

        if result.allowed:
            return executor(command)

        return None

    def request_approval(
        self,
        check_id: str,
        command: str,
        agent_id: Optional[str] = None,
        channel: str = "webhook",
        slack_channel: Optional[str] = None,
        email: Optional[str] = None,
        webhook_url: Optional[str] = None,
        timeout_minutes: int = 30,
        reason: Optional[str] = None,
    ) -> ApprovalResult:
        """
        Request approval for a high-risk command.

        Args:
            check_id: ID from the check response
            command: The command requiring approval
            agent_id: Agent identifier
            channel: Notification channel (slack, email, webhook)
            slack_channel: Slack channel for notification
            email: Email for notification
            webhook_url: Webhook URL for notification
            timeout_minutes: Approval timeout
            reason: Why the agent needs this command

        Returns:
            ApprovalResult with status and URLs
        """
        payload = {
            "check_id": check_id,
            "command": command,
            "agent_id": agent_id or self.config.agent_id or "default-agent",
            "channel": channel,
            "slack_channel": slack_channel,
            "email": email,
            "webhook_url": webhook_url,
            "timeout_minutes": timeout_minutes,
            "reason": reason,
        }

        response = self._client.post("/v1/approval/request", json=payload)
        data = self._handle_response(response)
        return ApprovalResult.from_dict(data)

    def get_approval_status(self, approval_id: str) -> ApprovalResult:
        """Get the status of an approval request."""
        response = self._client.get(f"/v1/approval/{approval_id}")
        data = self._handle_response(response)
        return ApprovalResult.from_dict(data)

    def wait_for_approval(
        self,
        approval_id: str,
        timeout: int = 300,
        poll_interval: int = 5,
    ) -> ApprovalResult:
        """
        Wait for an approval to be decided.

        Args:
            approval_id: The approval ID to wait for
            timeout: Maximum seconds to wait
            poll_interval: Seconds between status checks

        Returns:
            Final ApprovalResult

        Raises:
            ApprovalTimeoutError if timeout is reached
        """
        start = time.time()
        while time.time() - start < timeout:
            result = self.get_approval_status(approval_id)
            if not result.is_pending:
                return result
            time.sleep(poll_interval)

        raise ApprovalTimeoutError(approval_id)

    def create_policy(
        self,
        name: str,
        rules: List[Union[PolicyRule, Dict[str, Any]]],
        description: Optional[str] = None,
        environments: Optional[List[str]] = None,
    ) -> Policy:
        """
        Create a new safety policy.

        Args:
            name: Policy name
            rules: List of policy rules
            description: Policy description
            environments: Environments this policy applies to

        Returns:
            Created Policy
        """
        rule_dicts = []
        for rule in rules:
            if isinstance(rule, PolicyRule):
                rule_dicts.append(rule.to_dict())
            else:
                rule_dicts.append(rule)

        payload = {
            "name": name,
            "description": description,
            "rules": rule_dicts,
            "environments": environments,
        }

        response = self._client.post("/v1/policies", json=payload)
        data = self._handle_response(response)
        return Policy.from_dict(data)

    def list_policies(self) -> List[Policy]:
        """List all policies."""
        response = self._client.get("/v1/policies")
        data = self._handle_response(response)
        return [Policy.from_dict(p) for p in data.get("policies", [])]

    def get_policy(self, policy_id: str) -> Policy:
        """Get a specific policy."""
        response = self._client.get(f"/v1/policies/{policy_id}")
        data = self._handle_response(response)
        return Policy.from_dict(data)

    def get_audit_log(
        self,
        agent_id: Optional[str] = None,
        risk_level: Optional[str] = None,
        limit: int = 100,
        offset: int = 0,
    ) -> List[AuditEntry]:
        """
        Get the audit log.

        Args:
            agent_id: Filter by agent
            risk_level: Filter by risk level
            limit: Max entries to return
            offset: Pagination offset

        Returns:
            List of AuditEntry
        """
        params = {"limit": limit, "offset": offset}
        if agent_id:
            params["agent_id"] = agent_id
        if risk_level:
            params["risk_level"] = risk_level

        response = self._client.get("/v1/audit", params=params)
        data = self._handle_response(response)
        return [AuditEntry.from_dict(e) for e in data.get("entries", [])]

    def get_stats(self) -> Dict[str, Any]:
        """Get VasperaGuard statistics."""
        response = self._client.get("/v1/stats")
        return self._handle_response(response)

    def close(self):
        """Close the HTTP client."""
        self._client.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()


# Convenience function for quick checks
def check(command: str, **kwargs) -> CheckResult:
    """
    Quick check without creating a Guard instance.

    Uses VASPERA_GUARD_API_KEY environment variable.
    """
    with Guard() as guard:
        return guard.check(command, **kwargs)
