"""Async HTTP client for server communication."""

from __future__ import annotations

import ssl
from dataclasses import dataclass, field
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

import httpx
import orjson
import structlog

from curadise_agent.errors import AuthenticationError, TransportError
from curadise_agent.utils.retry import (
    CircuitBreaker,
    CircuitBreakerConfig,
    CircuitOpenError,
    RetryConfig,
    retry_with_backoff,
)

if TYPE_CHECKING:
    from pathlib import Path

    from curadise_agent.config.schema import AuthConfig, ServerConfig
    from curadise_agent.config.schema import RetryConfig as SchemaRetryConfig
    from curadise_agent.transport.batch import Batch

log = structlog.get_logger(__name__)


@dataclass
class ClientConfig:
    """HTTP client configuration."""

    base_url: str
    timeout: float = 30.0
    verify_ssl: bool = True
    ca_cert: Path | None = None
    mtls_cert: Path | None = None
    mtls_key: Path | None = None


@dataclass
class TransportStats:
    """Statistics about transport operations."""

    requests_sent: int = 0
    requests_successful: int = 0
    requests_failed: int = 0
    bytes_sent: int = 0
    bytes_received: int = 0
    batches_sent: int = 0
    metrics_sent: int = 0
    last_success_time: datetime | None = None
    last_failure_time: datetime | None = None


@dataclass
class TransportClient:
    """
    Async HTTP client for communicating with the Curadise server.

    Handles authentication, compression, retries, and circuit breaking.
    """

    config: ClientConfig
    retry_config: RetryConfig = field(default_factory=RetryConfig)
    circuit_breaker_config: CircuitBreakerConfig = field(default_factory=CircuitBreakerConfig)
    _client: httpx.AsyncClient | None = field(default=None, init=False)
    _circuit_breaker: CircuitBreaker = field(init=False)
    _stats: TransportStats = field(default_factory=TransportStats, init=False)
    _auth_token: str | None = field(default=None, init=False)

    def __post_init__(self) -> None:
        """Initialize circuit breaker."""
        self._circuit_breaker = CircuitBreaker(config=self.circuit_breaker_config)

    @property
    def stats(self) -> TransportStats:
        """Get transport statistics."""
        return self._stats

    @property
    def is_connected(self) -> bool:
        """Check if client is initialized."""
        return self._client is not None

    @property
    def circuit_state(self) -> str:
        """Get current circuit breaker state."""
        return self._circuit_breaker.state.value

    def set_auth_token(self, token: str) -> None:
        """Set the authentication token."""
        self._auth_token = token

    def clear_auth_token(self) -> None:
        """Clear the authentication token."""
        self._auth_token = None

    async def start(self) -> None:
        """Initialize the HTTP client."""
        if self._client is not None:
            return

        # Build SSL context
        ssl_context: ssl.SSLContext | bool = self.config.verify_ssl

        if self.config.verify_ssl:
            ssl_context = ssl.create_default_context()

            if self.config.ca_cert:
                ssl_context.load_verify_locations(self.config.ca_cert)

            if self.config.mtls_cert and self.config.mtls_key:
                ssl_context.load_cert_chain(
                    certfile=self.config.mtls_cert,
                    keyfile=self.config.mtls_key,
                )

        self._client = httpx.AsyncClient(
            base_url=self.config.base_url,
            timeout=httpx.Timeout(self.config.timeout),
            verify=ssl_context,
            http2=True,
        )

        log.info("transport_client_started", base_url=self.config.base_url)

    async def stop(self) -> None:
        """Close the HTTP client."""
        if self._client is not None:
            await self._client.aclose()
            self._client = None
            log.info("transport_client_stopped")

    async def _ensure_client(self) -> httpx.AsyncClient:
        """Ensure client is initialized."""
        if self._client is None:
            await self.start()
        assert self._client is not None
        return self._client

    def _get_headers(self, compressed: bool = False) -> dict[str, str]:
        """Build request headers."""
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": "curadise-agent/0.1.0",
        }

        if compressed:
            headers["Content-Encoding"] = "zstd"

        if self._auth_token:
            headers["Authorization"] = f"Bearer {self._auth_token}"

        return headers

    async def _request(
        self,
        method: str,
        path: str,
        *,
        data: bytes | None = None,
        json_data: dict[str, Any] | None = None,
        compressed: bool = False,
        headers: dict[str, str] | None = None,
    ) -> httpx.Response:
        """
        Make an HTTP request with circuit breaker protection.

        Args:
            method: HTTP method
            path: URL path
            data: Raw request body
            json_data: JSON request body (will be serialized)
            compressed: Whether data is compressed
            headers: Additional headers

        Returns:
            HTTP response

        Raises:
            TransportError: If request fails
            CircuitOpenError: If circuit breaker is open
        """
        if not await self._circuit_breaker.can_execute():
            self._stats.requests_failed += 1
            raise CircuitOpenError()

        client = await self._ensure_client()
        request_headers = self._get_headers(compressed)
        if headers:
            request_headers.update(headers)

        body = data
        if json_data is not None:
            body = orjson.dumps(json_data)

        self._stats.requests_sent += 1
        if body:
            self._stats.bytes_sent += len(body)

        try:
            response = await client.request(
                method=method,
                url=path,
                content=body,
                headers=request_headers,
            )

            self._stats.bytes_received += len(response.content)

            if response.status_code == 401:
                await self._circuit_breaker.record_failure()
                self._stats.requests_failed += 1
                self._stats.last_failure_time = datetime.now(UTC)
                raise AuthenticationError("Authentication failed", status_code=401)

            if response.status_code >= 500:
                await self._circuit_breaker.record_failure()
                self._stats.requests_failed += 1
                self._stats.last_failure_time = datetime.now(UTC)
                raise TransportError(
                    f"Server error: {response.status_code}",
                    status_code=response.status_code,
                )

            if response.status_code >= 400:
                self._stats.requests_failed += 1
                self._stats.last_failure_time = datetime.now(UTC)
                raise TransportError(
                    f"Client error: {response.status_code}",
                    status_code=response.status_code,
                )

            await self._circuit_breaker.record_success()
            self._stats.requests_successful += 1
            self._stats.last_success_time = datetime.now(UTC)

            return response

        except httpx.TimeoutException as e:
            await self._circuit_breaker.record_failure()
            self._stats.requests_failed += 1
            self._stats.last_failure_time = datetime.now(UTC)
            raise TransportError("Request timed out", cause=e) from e

        except httpx.HTTPError as e:
            await self._circuit_breaker.record_failure()
            self._stats.requests_failed += 1
            self._stats.last_failure_time = datetime.now(UTC)
            raise TransportError(f"HTTP error: {e}", cause=e) from e

    async def request_with_retry(
        self,
        method: str,
        path: str,
        *,
        data: bytes | None = None,
        json_data: dict[str, Any] | None = None,
        compressed: bool = False,
        headers: dict[str, str] | None = None,
    ) -> httpx.Response:
        """Make a request with retry logic."""
        return await retry_with_backoff(
            self._request,
            method,
            path,
            data=data,
            json_data=json_data,
            compressed=compressed,
            headers=headers,
            config=self.retry_config,
        )

    async def send_batch(self, batch: Batch) -> bool:
        """
        Send a batch of metrics to the server.

        Args:
            batch: Batch to send

        Returns:
            True if successful
        """
        try:
            response = await self.request_with_retry(
                "POST",
                "/api/v1/metrics",
                data=batch.data,
                compressed=batch.compressed,
            )

            self._stats.batches_sent += 1
            self._stats.metrics_sent += batch.size

            log.debug(
                "batch_sent",
                batch_id=batch.id,
                metric_count=batch.size,
                status_code=response.status_code,
            )
            return True

        except Exception as e:
            log.error("batch_send_failed", batch_id=batch.id, error=str(e))
            raise

    async def send_heartbeat(self, payload: dict[str, Any]) -> dict[str, Any]:
        """
        Send a heartbeat to the server.

        Args:
            payload: Heartbeat payload

        Returns:
            Server response data
        """
        response = await self.request_with_retry(
            "POST",
            "/api/v1/heartbeat",
            json_data=payload,
        )

        result: dict[str, Any] = orjson.loads(response.content)
        return result

    async def register(
        self,
        enrollment_token: str,
        system_info: dict[str, Any],
        tags: dict[str, str] | None = None,
    ) -> dict[str, Any]:
        """
        Register the agent with the server.

        Args:
            enrollment_token: Enrollment token
            system_info: System information
            tags: Agent tags

        Returns:
            Registration response with agent_id and tokens
        """
        payload = {
            "enrollment_token": enrollment_token,
            "system_info": system_info,
            "tags": tags or {},
        }

        response = await self._request(
            "POST",
            "/api/v1/agents/register",
            json_data=payload,
        )

        result: dict[str, Any] = orjson.loads(response.content)
        return result

    async def fetch_commands(self) -> list[dict[str, Any]]:
        """
        Fetch pending commands from the server.

        Returns:
            List of command objects
        """
        response = await self.request_with_retry(
            "GET",
            "/api/v1/commands/pending",
        )

        data: dict[str, Any] = orjson.loads(response.content)
        commands: list[dict[str, Any]] = data.get("commands", [])
        return commands

    async def report_command_result(
        self,
        command_id: str,
        result: dict[str, Any],
    ) -> None:
        """
        Report command execution result to the server.

        Args:
            command_id: Command ID
            result: Execution result
        """
        await self.request_with_retry(
            "POST",
            f"/api/v1/commands/{command_id}/result",
            json_data=result,
        )

    async def health_check(self) -> bool:
        """
        Check server health.

        Returns:
            True if server is healthy
        """
        try:
            response = await self._request("GET", "/health")
            return response.status_code == 200
        except Exception:
            return False


def create_transport_client(
    server_config: ServerConfig,
    auth_config: AuthConfig,
    retry_config: SchemaRetryConfig,
) -> TransportClient:
    """
    Factory function to create a configured transport client.

    Args:
        server_config: Server connection configuration
        auth_config: Authentication configuration
        retry_config: Retry configuration

    Returns:
        Configured TransportClient
    """
    client_config = ClientConfig(
        base_url=server_config.url,
        timeout=server_config.timeout,
        verify_ssl=server_config.verify_ssl,
        ca_cert=server_config.ca_cert,
        mtls_cert=auth_config.mtls_cert,
        mtls_key=auth_config.mtls_key,
    )

    return TransportClient(
        config=client_config,
        retry_config=RetryConfig(
            max_attempts=retry_config.max_attempts,
            initial_delay=retry_config.initial_delay,
            max_delay=retry_config.max_delay,
            exponential_base=retry_config.exponential_base,
        ),
        circuit_breaker_config=CircuitBreakerConfig(
            failure_threshold=retry_config.circuit_breaker_threshold,
            recovery_timeout=retry_config.circuit_breaker_timeout,
        ),
    )
