"""HTTP clients for the WL-Guardrails content safety service.

This module provides both async and sync HTTP clients for interacting with
the WL-Guardrails API.

Example (async):
    >>> async with WlGuardrailsClient("http://localhost:8083") as client:
    ...     result = await client.check_input("Hello, world!")
    ...     print(result.action)  # "pass"

Example (sync):
    >>> with WlGuardrailsSyncClient("http://localhost:8083") as client:
    ...     result = client.check_input("Hello, world!")
    ...     print(result.action)  # "pass"
"""

from __future__ import annotations

import logging
import os
from typing import Any

import httpx

from .exceptions import GuardrailBlockError, ServiceUnavailable, ValidationError
from .models import CheckResult, GuardrailAction, HealthResponse

logger = logging.getLogger("wl_guardrails")


class WlGuardrailsClient:
    """Async client for the WL-Guardrails content safety service.

    Attributes:
        base_url: Base URL of the guardrails service.
        timeout: Request timeout in seconds.
        fail_mode: "open" to proceed on service errors, "closed" to raise.

    Example:
        >>> async with WlGuardrailsClient() as client:
        ...     result = await client.check_input("user message")
        ...     if result.is_blocked:
        ...         print("Content blocked!")
    """

    def __init__(
        self,
        base_url: str | None = None,
        timeout: float = 5.0,
        fail_mode: str | None = None,
    ) -> None:
        self.base_url = (
            base_url or os.getenv("WL_GUARDRAILS_URL", "http://localhost:8083")
        ).rstrip("/")
        self.timeout = timeout
        self.fail_mode = fail_mode or os.getenv("WL_GUARDRAILS_FAIL_MODE", "open")
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> WlGuardrailsClient:
        self._client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=self.timeout,
        )
        return self

    async def __aexit__(self, *args: Any) -> None:
        if self._client:
            await self._client.aclose()
            self._client = None

    def _ensure_client(self) -> httpx.AsyncClient:
        if self._client is None:
            raise RuntimeError(
                "Client not initialized. Use 'async with WlGuardrailsClient() as client:'"
            )
        return self._client

    async def check_input(
        self,
        content: str,
        request_id: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> CheckResult:
        """Check input content against guardrail policies.

        Args:
            content: The user/agent message to check.
            request_id: Optional trace correlation ID.
            metadata: Optional metadata for policy context.

        Returns:
            CheckResult with action, violations, and request_id.

        Raises:
            GuardrailBlockError: If content is blocked.
            ServiceUnavailable: If the service is unreachable (fail_mode="closed").
        """
        return await self._check("/v1/check-input", content, request_id, metadata)

    async def check_output(
        self,
        content: str,
        request_id: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> CheckResult:
        """Check LLM output content against guardrail policies.

        Args:
            content: The LLM response to check.
            request_id: Optional trace correlation ID.
            metadata: Optional metadata for policy context.

        Returns:
            CheckResult with action, violations, sanitized content, and request_id.

        Raises:
            GuardrailBlockError: If content is blocked.
            ServiceUnavailable: If the service is unreachable (fail_mode="closed").
        """
        return await self._check("/v1/check-output", content, request_id, metadata)

    async def health(self) -> bool:
        """Check if the guardrails service is healthy.

        Returns:
            True if healthy, False otherwise.
        """
        client = self._ensure_client()
        try:
            response = await client.get("/health")
            return response.status_code == 200
        except Exception:
            return False

    async def get_health(self) -> HealthResponse:
        """Get detailed health information.

        Returns:
            HealthResponse with status details.

        Raises:
            ServiceUnavailable: If the service is unreachable.
        """
        client = self._ensure_client()
        try:
            response = await client.get("/health")
            response.raise_for_status()
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return HealthResponse(**response.json())

    async def _check(
        self,
        endpoint: str,
        content: str,
        request_id: str | None,
        metadata: dict[str, Any] | None,
    ) -> CheckResult:
        """Internal method for check-input/check-output."""
        client = self._ensure_client()
        payload: dict[str, Any] = {"content": content}
        if request_id:
            payload["request_id"] = request_id
        if metadata:
            payload["metadata"] = metadata

        try:
            resp = await client.post(endpoint, json=payload)
            if resp.status_code == 400:
                data = resp.json()
                raise ValidationError(
                    data.get("message", "Invalid request"),
                    error_code=data.get("error"),
                )
            resp.raise_for_status()
            result = CheckResult(**resp.json())

            if result.action == GuardrailAction.BLOCK:
                logger.warning(
                    "Content BLOCKED: %s (request_id=%s)",
                    [v.error_code for v in result.violations],
                    result.request_id,
                )
                raise GuardrailBlockError(result)

            if result.action == GuardrailAction.SANITIZE:
                logger.info(
                    "Content SANITIZED: %s (request_id=%s)",
                    [v.error_code for v in result.violations],
                    result.request_id,
                )

            return result

        except (GuardrailBlockError, ValidationError):
            raise
        except httpx.ConnectError as e:
            if self.fail_mode == "open":
                logger.warning("Fail-open: guardrails service unreachable, proceeding")
                return CheckResult(
                    action=GuardrailAction.PASS,
                    request_id=request_id or "",
                )
            raise ServiceUnavailable(url=self.base_url) from e
        except Exception as e:
            logger.error("Guardrails check failed: %s", e)
            if self.fail_mode == "open":
                logger.warning("Fail-open: proceeding without guardrails")
                return CheckResult(
                    action=GuardrailAction.PASS,
                    request_id=request_id or "",
                )
            raise


class WlGuardrailsSyncClient:
    """Synchronous client for the WL-Guardrails content safety service.

    Example:
        >>> with WlGuardrailsSyncClient() as client:
        ...     result = client.check_input("user message")
        ...     if result.is_blocked:
        ...         print("Content blocked!")
    """

    def __init__(
        self,
        base_url: str | None = None,
        timeout: float = 5.0,
        fail_mode: str | None = None,
    ) -> None:
        self.base_url = (
            base_url or os.getenv("WL_GUARDRAILS_URL", "http://localhost:8083")
        ).rstrip("/")
        self.timeout = timeout
        self.fail_mode = fail_mode or os.getenv("WL_GUARDRAILS_FAIL_MODE", "open")
        self._client = httpx.Client(
            base_url=self.base_url,
            timeout=self.timeout,
        )

    def __enter__(self) -> WlGuardrailsSyncClient:
        return self

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

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

    def check_input(
        self,
        content: str,
        request_id: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> CheckResult:
        """Check input content against guardrail policies.

        Args:
            content: The user/agent message to check.
            request_id: Optional trace correlation ID.
            metadata: Optional metadata for policy context.

        Returns:
            CheckResult with action, violations, and request_id.

        Raises:
            GuardrailBlockError: If content is blocked.
            ServiceUnavailable: If the service is unreachable (fail_mode="closed").
        """
        return self._check("/v1/check-input", content, request_id, metadata)

    def check_output(
        self,
        content: str,
        request_id: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> CheckResult:
        """Check LLM output content against guardrail policies.

        Args:
            content: The LLM response to check.
            request_id: Optional trace correlation ID.
            metadata: Optional metadata for policy context.

        Returns:
            CheckResult with action, violations, sanitized content, and request_id.

        Raises:
            GuardrailBlockError: If content is blocked.
            ServiceUnavailable: If the service is unreachable (fail_mode="closed").
        """
        return self._check("/v1/check-output", content, request_id, metadata)

    def health(self) -> bool:
        """Check if the guardrails service is healthy.

        Returns:
            True if healthy, False otherwise.
        """
        try:
            response = self._client.get("/health")
            return response.status_code == 200
        except Exception:
            return False

    def get_health(self) -> HealthResponse:
        """Get detailed health information.

        Returns:
            HealthResponse with status details.

        Raises:
            ServiceUnavailable: If the service is unreachable.
        """
        try:
            response = self._client.get("/health")
            response.raise_for_status()
        except httpx.ConnectError as e:
            raise ServiceUnavailable(url=self.base_url) from e
        return HealthResponse(**response.json())

    def _check(
        self,
        endpoint: str,
        content: str,
        request_id: str | None,
        metadata: dict[str, Any] | None,
    ) -> CheckResult:
        """Internal method for check-input/check-output."""
        payload: dict[str, Any] = {"content": content}
        if request_id:
            payload["request_id"] = request_id
        if metadata:
            payload["metadata"] = metadata

        try:
            resp = self._client.post(endpoint, json=payload)
            if resp.status_code == 400:
                data = resp.json()
                raise ValidationError(
                    data.get("message", "Invalid request"),
                    error_code=data.get("error"),
                )
            resp.raise_for_status()
            result = CheckResult(**resp.json())

            if result.action == GuardrailAction.BLOCK:
                logger.warning(
                    "Content BLOCKED: %s (request_id=%s)",
                    [v.error_code for v in result.violations],
                    result.request_id,
                )
                raise GuardrailBlockError(result)

            if result.action == GuardrailAction.SANITIZE:
                logger.info(
                    "Content SANITIZED: %s (request_id=%s)",
                    [v.error_code for v in result.violations],
                    result.request_id,
                )

            return result

        except (GuardrailBlockError, ValidationError):
            raise
        except httpx.ConnectError as e:
            if self.fail_mode == "open":
                logger.warning("Fail-open: guardrails service unreachable, proceeding")
                return CheckResult(
                    action=GuardrailAction.PASS,
                    request_id=request_id or "",
                )
            raise ServiceUnavailable(url=self.base_url) from e
        except Exception as e:
            logger.error("Guardrails check failed: %s", e)
            if self.fail_mode == "open":
                logger.warning("Fail-open: proceeding without guardrails")
                return CheckResult(
                    action=GuardrailAction.PASS,
                    request_id=request_id or "",
                )
            raise
