"""Main ingress client for sending data to vikdata ingress cores.

IngressClient is the main client for sending data to vikdata ingress cores.

Features:
- Auto-batching: Batches points by size (100) or timeout (5s)
- Retry logic: Exponential backoff with max 5 attempts
- Offline buffering: Stores up to 10,000 points when offline
- Pydantic validation: Type-safe with runtime validation
"""

import httpx

from .batch import BatchManager
from .offline import OfflineBuffer
from .retry import RetryManager
from .types import (
    BufferStats,
    IngressBatchRequest,
    IngressClientConfig,
    IngressDataPoint,
    IngressResponse,
)


class IngressClient:
    """Main client for sending data to ingress cores."""

    def __init__(self, config: IngressClientConfig) -> None:
        """Initialize ingress client.

        Args:
            config: Client configuration
        """
        # Normalize base_url: remove trailing slash
        self._base_url = config.base_url.rstrip("/")
        self._api_key = config.api_key
        self._is_online = True

        # Initialize HTTP client
        self._http_client = httpx.AsyncClient(
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {self._api_key}",
            }
        )

        # Initialize managers
        self._retry_manager = RetryManager(config.max_retries)
        self._offline_buffer = OfflineBuffer(config.offline_buffer_size)
        self._batch_manager = BatchManager(
            batch_size=config.batch_size,
            batch_timeout=config.batch_timeout,
            on_flush=self._send_batch,
        )

    async def send(self, point: IngressDataPoint) -> None:
        """Send a single data point to the ingress core.

        The point will be auto-batched with other points.

        Args:
            point: Data point to send

        Raises:
            ValidationError: If validation fails
        """
        # Pydantic validates automatically when creating IngressDataPoint
        # No need for explicit parse like Zod

        if self._is_online:
            await self._batch_manager.add(point)
        else:
            self._offline_buffer.add(point)

    async def flush(self) -> None:
        """Force flush the current batch immediately."""
        await self._batch_manager.flush()

    def get_buffer_stats(self) -> BufferStats:
        """Get offline buffer statistics.

        Returns:
            Buffer statistics
        """
        return self._offline_buffer.get_stats()

    async def drain_offline_buffer(self) -> None:
        """Drain the offline buffer and send all buffered points.

        Called when the client comes back online.

        Points are sent in batches of 1000 to respect the ingress core limit.
        """
        points = self._offline_buffer.drain()
        if len(points) == 0:
            return

        # Send in batches of 1000 (ingress core max)
        BATCH_SIZE = 1000
        for i in range(0, len(points), BATCH_SIZE):
            batch = points[i : i + BATCH_SIZE]
            await self._send_batch(batch)

    async def set_online(self, online: bool) -> None:
        """Set the online status of the client.

        When coming online, automatically drains the offline buffer.

        Args:
            online: Whether the client is online
        """
        was_offline = not self._is_online
        self._is_online = online

        # If coming back online, drain offline buffer
        if online and was_offline:
            await self.drain_offline_buffer()

    async def _send_batch(self, points: list[IngressDataPoint]) -> IngressResponse:
        """Internal method to send a batch of points to the ingress core.

        Args:
            points: Array of data points to send

        Returns:
            Response from ingress core

        Raises:
            Exception: If request fails after all retries
        """
        request = IngressBatchRequest(points=points)

        # Execute with retry logic
        async def execute_request() -> IngressResponse:
            response = await self._http_client.post(
                f"{self._base_url}/ingest",
                json=request.model_dump(mode="json"),
            )

            # Raise for 4xx/5xx status codes
            response.raise_for_status()

            # Parse and validate response
            data = response.json()
            return IngressResponse(**data)

        return await self._retry_manager.execute(execute_request)

    def get_pending_count(self) -> int:
        """Get the number of points currently pending in the batch.

        Returns:
            Number of pending points
        """
        return self._batch_manager.get_pending_count()

    def is_client_online(self) -> bool:
        """Check if the client is currently online.

        Returns:
            True if client is online
        """
        return self._is_online

    async def close(self) -> None:
        """Close the HTTP client and clean up resources."""
        await self._http_client.aclose()

    async def __aenter__(self) -> "IngressClient":
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """Async context manager exit."""
        await self.close()
