"""
HTTP exporter for sending spans to the Risicare backend.

Uses httpx for HTTP/2 support and async capabilities.
"""

from __future__ import annotations

import json
import threading
import time
from typing import Any, Dict, List, Optional, TYPE_CHECKING

import logging

from risicare.exporters.base import ExportResult, SpanExporter

logger = logging.getLogger("risicare.exporters.http")

if TYPE_CHECKING:
    from risicare_core import Span


class HttpExporter(SpanExporter):
    """
    Exporter that sends spans to the Risicare HTTP API.

    Features:
    - HTTP/2 support via httpx
    - Automatic retry with exponential backoff
    - Circuit breaker for sustained failures
    - Compression for large batches
    - API key authentication
    """

    def __init__(
        self,
        endpoint: str = "https://app.risicare.ai",
        api_key: Optional[str] = None,
        project_id: Optional[str] = None,
        environment: Optional[str] = None,
        timeout_seconds: float = 5.0,
        max_retries: int = 3,
        compress: bool = False,
    ):
        """
        Initialize the HTTP exporter.

        Args:
            endpoint: API endpoint URL.
            api_key: API key for authentication.
            project_id: Optional project ID (can also be in API key claims).
            environment: Optional environment name (production, staging, etc.).
            timeout_seconds: Request timeout (default reduced to 5s).
            max_retries: Maximum retry attempts.
            compress: Whether to gzip compress requests.
        """
        self._endpoint = endpoint.rstrip("/")
        self._api_key = api_key
        self._project_id = project_id
        self._environment = environment
        self._timeout = timeout_seconds
        self._max_retries = max_retries
        self._compress = compress
        self._client: Optional[Any] = None

        # Circuit breaker state (protected by _cb_lock for thread safety — P2-4)
        self._cb_lock = threading.Lock()
        self._consecutive_failures = 0
        self._circuit_open_until = 0.0  # monotonic timestamp
        self._circuit_breaker_threshold = 3
        self._circuit_breaker_cooldown = 30.0  # seconds

    def _get_client(self):
        """Get or create HTTP client (lazy initialization)."""
        if self._client is None:
            try:
                import httpx
                try:
                    self._client = httpx.Client(
                        timeout=self._timeout,
                        http2=True,
                    )
                except Exception:
                    # h2 package not installed — fall back to HTTP/1.1
                    self._client = httpx.Client(
                        timeout=self._timeout,
                    )
            except ImportError:
                # Fallback to urllib if httpx not available
                self._client = "urllib"
        return self._client

    def export(self, spans: List["Span"]) -> ExportResult:
        """Export spans to the Risicare API."""
        if not spans:
            return ExportResult.SUCCESS

        # Circuit breaker: skip export if circuit is open (P2-4: thread-safe)
        with self._cb_lock:
            now = time.monotonic()
            if self._consecutive_failures >= self._circuit_breaker_threshold:
                if now < self._circuit_open_until:
                    return ExportResult.FAILURE
                # Cooldown expired, allow one attempt (half-open)

        # Serialize spans (outside lock — may be slow)
        payload = {
            "spans": [span.to_dict() for span in spans],
        }
        if self._project_id:
            payload["projectId"] = self._project_id
        if self._environment:
            payload["environment"] = self._environment

        # Build headers
        headers = {
            "Content-Type": "application/json",
        }
        if self._api_key:
            headers["Authorization"] = f"Bearer {self._api_key}"

        # Attempt export with retries and exponential backoff
        for attempt in range(self._max_retries):
            result = self._send_request(payload, headers)
            if result == ExportResult.SUCCESS:
                with self._cb_lock:
                    self._consecutive_failures = 0
                # P2-7: Self-observability
                try:
                    from risicare_core.observability import export_success_total

                    export_success_total.inc({"exporter": "http"})
                except Exception:
                    pass
                return result

            # Exponential backoff (0.1s, 0.2s, 0.4s)
            if attempt < self._max_retries - 1:
                time.sleep(0.1 * (2 ** attempt))

        # All retries failed — update circuit breaker (P2-4: thread-safe)
        with self._cb_lock:
            self._consecutive_failures += 1
            if self._consecutive_failures >= self._circuit_breaker_threshold:
                self._circuit_open_until = time.monotonic() + self._circuit_breaker_cooldown

        # P2-7: Self-observability
        try:
            from risicare_core.observability import export_failure_total

            export_failure_total.inc({"exporter": "http"})
        except Exception:
            pass

        return ExportResult.FAILURE

    def _send_request(
        self,
        payload: Dict[str, Any],
        headers: Dict[str, str],
    ) -> ExportResult:
        """Send HTTP request."""
        # Use /v1/spans endpoint which accepts JSON array format
        # (not /v1/spans/batch which expects NDJSON)
        url = f"{self._endpoint}/v1/spans"
        body = json.dumps(payload).encode("utf-8")

        # Compress if enabled and large enough
        if self._compress and len(body) > 1024:
            import gzip
            body = gzip.compress(body)
            headers["Content-Encoding"] = "gzip"

        try:
            client = self._get_client()

            if client == "urllib":
                return self._send_urllib(url, body, headers)
            else:
                return self._send_httpx(url, body, headers)

        except Exception as e:
            logger.debug("HTTP export request failed: %s", e)
            return ExportResult.FAILURE

    def _send_httpx(
        self,
        url: str,
        body: bytes,
        headers: Dict[str, str],
    ) -> ExportResult:
        """Send using httpx."""
        try:
            response = self._client.post(
                url,
                content=body,
                headers=headers,
            )
            if response.status_code < 300:
                return ExportResult.SUCCESS
            elif response.status_code == 408 or response.status_code == 504:
                logger.debug("HTTP export timeout: %d", response.status_code)
                return ExportResult.TIMEOUT
            else:
                logger.debug("HTTP export failed: %d", response.status_code)
                return ExportResult.FAILURE
        except Exception as e:
            logger.debug("HTTP export error: %s", e)
            return ExportResult.FAILURE

    def _send_urllib(
        self,
        url: str,
        body: bytes,
        headers: Dict[str, str],
    ) -> ExportResult:
        """Fallback using urllib."""
        import urllib.request
        import urllib.error

        try:
            request = urllib.request.Request(
                url,
                data=body,
                headers=headers,
                method="POST",
            )
            with urllib.request.urlopen(request, timeout=self._timeout) as response:
                if response.status < 300:
                    return ExportResult.SUCCESS
                else:
                    return ExportResult.FAILURE
        except urllib.error.HTTPError as e:
            if e.code == 408 or e.code == 504:
                logger.debug("HTTP export timeout (urllib): %d", e.code)
                return ExportResult.TIMEOUT
            logger.debug("HTTP export failed (urllib): %d", e.code)
            return ExportResult.FAILURE
        except Exception as e:
            logger.debug("HTTP export error (urllib): %s", e)
            return ExportResult.FAILURE

    def shutdown(self) -> None:
        """Close the HTTP client."""
        if self._client and self._client != "urllib":
            try:
                self._client.close()
            except Exception:
                pass
            self._client = None
