"""Synchronous client for Miruvor SDK."""
import logging
import time
from typing import Any, Dict, List, Optional, Union

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

from ._http import parse_json_or_raise
from .exceptions import MiruvorError, RateLimitError, ServerError
from .models import (
    IngestRequest,
    IngestResponse,
    RetrieveResponse,
    StoreRequest,
    StoreResponse,
)

logger = logging.getLogger("miruvor")


class MiruvorClient:
    """Synchronous client for Zyra SNN Memory Database."""

    def __init__(
        self,
        base_url: str,
        api_key: str,
        token: Optional[str] = None,
        timeout: float = 30.0,
        max_retries: int = 3,
    ):
        """
        Initialize Miruvor client.

        Args:
            base_url: API base URL (e.g., "https://api.example.com")
            api_key: X-API-Key for authentication
            token: Optional JWT token for tenant-scoped operations
            timeout: Request timeout in seconds
            max_retries: Max retry attempts for failed requests
        """
        self.base_url = base_url.rstrip("/")
        self.api_key = api_key
        self.token = token
        self.timeout = timeout
        self.max_retries = max_retries

        self.session = requests.Session()
        self.session.headers.update({"X-API-Key": api_key})
        if token:
            self.session.headers.update({"Authorization": f"Bearer {token}"})

        retry_strategy = Retry(
            total=max_retries,
            backoff_factor=0.5,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["POST", "GET"],
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        self.session.mount("https://", adapter)
        self.session.mount("http://", adapter)

    def _request(
        self,
        method: str,
        path: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Any:
        url = f"{self.base_url}{path}"
        for attempt in range(self.max_retries + 1):
            try:
                resp = self.session.request(
                    method,
                    url,
                    json=json,
                    params=params,
                    timeout=self.timeout,
                )
                return parse_json_or_raise(resp)
            except (RateLimitError, ServerError) as e:
                if attempt >= self.max_retries:
                    logger.error(f"{method} {path} failed: {e.message} (status={e.status_code})")
                    raise
                delay = self._retry_delay_seconds(attempt, e)
                logger.warning(f"{method} {path} retrying in {delay:.2f}s after {e.status_code}")
                time.sleep(delay)
            except requests.RequestException as e:
                if attempt >= self.max_retries:
                    raise MiruvorError(f"HTTP request failed: {e}") from e
                delay = self._retry_delay_seconds(attempt, None)
                logger.warning(f"{method} {path} network retry in {delay:.2f}s: {e}")
                time.sleep(delay)
            except MiruvorError as e:
                logger.error(f"{method} {path} failed: {e.message} (status={e.status_code})")
                raise
        raise MiruvorError(f"{method} {path} failed after retries")

    def _retry_delay_seconds(self, attempt: int, error: Optional[MiruvorError]) -> float:
        if isinstance(error, RateLimitError):
            return float(max(error.retry_after, 1))
        return min(8.0, 0.5 * (2**attempt))

    def health(self) -> Dict[str, Any]:
        """Check API health status."""
        logger.debug("Checking API health")
        return self._request("GET", "/health")

    def store(
        self,
        text: str,
        tags: Optional[List[str]] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> StoreResponse:
        clean_text = text.strip()
        if not clean_text:
            raise ValueError("Text cannot be empty")
        logger.debug(f"Storing memory: {clean_text[:50]}...")
        payload = StoreRequest(
            text=clean_text,
            tags=tags or [],
            metadata=metadata or {},
        ).model_dump()
        data = self._request("POST", "/store", json=payload)
        logger.debug(f"Stored memory: {data.get('memory_id')}")
        return StoreResponse(**data)

    def store_batch(
        self,
        memories: List[Dict[str, Any]],
    ) -> List[StoreResponse]:
        logger.debug(f"Storing {len(memories)} memories in batch")
        return [
            self.store(
                text=m["text"],
                tags=m.get("tags", []),
                metadata=m.get("metadata", {}),
            )
            for m in memories
        ]

    def ingest(
        self,
        content: Union[str, dict],
        priority: str = "normal",
        metadata: Optional[Dict[str, Any]] = None,
        enable_consolidation: bool = True,
        enable_background_linking: bool = True,
    ) -> IngestResponse:
        logger.debug(f"Ingesting content with priority={priority}")
        payload = IngestRequest(
            content=content,
            priority=priority,
            metadata=metadata or {},
            enable_consolidation=enable_consolidation,
            enable_background_linking=enable_background_linking,
        ).model_dump()
        data = self._request("POST", "/ingest", json=payload)
        logger.debug(f"Ingested: message_id={data.get('message_id')}")
        return IngestResponse(**data)

    def retrieve(
        self,
        query: str,
        top_k: int = 5,
        use_sparse: Optional[bool] = None,
    ) -> RetrieveResponse:
        logger.debug(f"Retrieving memories for query: {query[:50]}...")
        params: Dict[str, Any] = {"query": query, "top_k": top_k}
        if use_sparse is not None:
            params["use_sparse"] = use_sparse
        data = self._request("POST", "/retrieve/pattern_completion", params=params)
        logger.debug(f"Retrieved {data.get('num_results', 0)} memories")
        return RetrieveResponse(**data)

    def close(self) -> None:
        """Close the session."""
        self.session.close()

    def __enter__(self) -> "MiruvorClient":
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        self.close()
