"""
Synor Database SDK Client
"""

from typing import Any, Dict, List, Optional, Union
from datetime import datetime
from urllib.parse import urlencode
import httpx

from .types import (
    DatabaseConfig,
    Network,
    KeyValueEntry,
    SetOptions,
    ListOptions,
    ListResult,
    Document,
    QueryOptions,
    QueryResult,
    VectorEntry,
    SearchOptions,
    SearchResult,
    VectorCollectionConfig,
    VectorCollectionStats,
    DataPoint,
    TimeRange,
    TimeSeriesQueryOptions,
    TimeSeriesResult,
    SeriesInfo,
    DatabaseStats,
    DatabaseError,
    Aggregation,
    SortOrder,
)


class KeyValueStore:
    """Key-Value store operations."""

    def __init__(self, client: "SynorDatabase"):
        self._client = client

    async def get(self, key: str) -> Optional[Any]:
        """Get a value by key."""
        response = await self._client._request("GET", f"/kv/{key}")
        return response.get("value")

    async def get_entry(self, key: str) -> Optional[KeyValueEntry]:
        """Get a full entry with metadata."""
        response = await self._client._request("GET", f"/kv/{key}/entry")
        if response is None:
            return None
        return KeyValueEntry(
            key=response["key"],
            value=response["value"],
            created_at=response["created_at"],
            updated_at=response["updated_at"],
            metadata=response.get("metadata"),
            expires_at=response.get("expires_at"),
        )

    async def set(
        self,
        key: str,
        value: Any,
        ttl: Optional[int] = None,
        metadata: Optional[Dict[str, str]] = None,
        if_not_exists: bool = False,
        if_exists: bool = False,
    ) -> None:
        """Set a value."""
        body = {"value": value}
        if ttl is not None:
            body["ttl"] = ttl
        if metadata is not None:
            body["metadata"] = metadata
        if if_not_exists:
            body["if_not_exists"] = True
        if if_exists:
            body["if_exists"] = True

        await self._client._request("PUT", f"/kv/{key}", body=body)

    async def delete(self, key: str) -> bool:
        """Delete a key."""
        response = await self._client._request("DELETE", f"/kv/{key}")
        return response.get("deleted", False)

    async def exists(self, key: str) -> bool:
        """Check if a key exists."""
        response = await self._client._request("GET", f"/kv/{key}/exists")
        return response.get("exists", False)

    async def list(
        self,
        prefix: Optional[str] = None,
        cursor: Optional[str] = None,
        limit: int = 100,
    ) -> ListResult:
        """List keys with optional prefix filtering."""
        params = {"limit": str(limit)}
        if prefix:
            params["prefix"] = prefix
        if cursor:
            params["cursor"] = cursor

        response = await self._client._request("GET", f"/kv?{urlencode(params)}")
        return ListResult(
            entries=[
                KeyValueEntry(
                    key=e["key"],
                    value=e["value"],
                    created_at=e["created_at"],
                    updated_at=e["updated_at"],
                    metadata=e.get("metadata"),
                    expires_at=e.get("expires_at"),
                )
                for e in response.get("entries", [])
            ],
            cursor=response.get("cursor"),
            has_more=response.get("has_more", False),
        )

    async def mget(self, keys: List[str]) -> Dict[str, Optional[Any]]:
        """Get multiple values at once."""
        response = await self._client._request("POST", "/kv/mget", body={"keys": keys})
        return {e["key"]: e["value"] for e in response.get("entries", [])}

    async def mset(self, entries: Dict[str, Any]) -> None:
        """Set multiple values at once."""
        body = {"entries": [{"key": k, "value": v} for k, v in entries.items()]}
        await self._client._request("POST", "/kv/mset", body=body)

    async def incr(self, key: str, by: int = 1) -> int:
        """Increment a numeric value."""
        response = await self._client._request("POST", f"/kv/{key}/incr", body={"by": by})
        return response["value"]


class DocumentStore:
    """Document store operations."""

    def __init__(self, client: "SynorDatabase"):
        self._client = client

    async def create(
        self,
        collection: str,
        data: Dict[str, Any],
        id: Optional[str] = None,
        metadata: Optional[Dict[str, str]] = None,
    ) -> Document:
        """Create a new document."""
        body: Dict[str, Any] = {"data": data}
        if id:
            body["id"] = id
        if metadata:
            body["metadata"] = metadata

        response = await self._client._request("POST", f"/documents/{collection}", body=body)
        return self._parse_document(response)

    async def get(self, collection: str, id: str) -> Optional[Document]:
        """Get a document by ID."""
        try:
            response = await self._client._request("GET", f"/documents/{collection}/{id}")
            return self._parse_document(response) if response else None
        except DatabaseError as e:
            if e.status_code == 404:
                return None
            raise

    async def update(
        self,
        collection: str,
        id: str,
        update: Dict[str, Any],
        upsert: bool = False,
    ) -> Document:
        """Update a document."""
        body = {"update": update, "upsert": upsert}
        response = await self._client._request("PATCH", f"/documents/{collection}/{id}", body=body)
        return self._parse_document(response)

    async def replace(self, collection: str, id: str, data: Dict[str, Any]) -> Document:
        """Replace a document."""
        response = await self._client._request("PUT", f"/documents/{collection}/{id}", body={"data": data})
        return self._parse_document(response)

    async def delete(self, collection: str, id: str) -> bool:
        """Delete a document."""
        response = await self._client._request("DELETE", f"/documents/{collection}/{id}")
        return response.get("deleted", False)

    async def query(
        self,
        collection: str,
        filter: Optional[Dict[str, Any]] = None,
        sort: Optional[Dict[str, str]] = None,
        skip: int = 0,
        limit: int = 100,
        projection: Optional[List[str]] = None,
    ) -> QueryResult:
        """Query documents."""
        body: Dict[str, Any] = {"skip": skip, "limit": limit}
        if filter:
            body["filter"] = filter
        if sort:
            body["sort"] = sort
        if projection:
            body["projection"] = projection

        response = await self._client._request("POST", f"/documents/{collection}/query", body=body)
        return QueryResult(
            documents=[self._parse_document(d) for d in response.get("documents", [])],
            total=response.get("total", 0),
            has_more=response.get("has_more", False),
        )

    async def count(self, collection: str, filter: Optional[Dict[str, Any]] = None) -> int:
        """Count documents matching a filter."""
        body = {"filter": filter} if filter else {}
        response = await self._client._request("POST", f"/documents/{collection}/count", body=body)
        return response.get("count", 0)

    async def aggregate(
        self,
        collection: str,
        pipeline: List[Dict[str, Any]],
    ) -> List[Any]:
        """Run an aggregation pipeline."""
        response = await self._client._request(
            "POST", f"/documents/{collection}/aggregate", body={"pipeline": pipeline}
        )
        return response.get("results", [])

    def _parse_document(self, data: Dict[str, Any]) -> Document:
        return Document(
            id=data["id"],
            data=data["data"],
            created_at=data["created_at"],
            updated_at=data["updated_at"],
            version=data["version"],
            metadata=data.get("metadata"),
        )


class VectorStore:
    """Vector store operations."""

    def __init__(self, client: "SynorDatabase"):
        self._client = client

    async def create_collection(
        self,
        name: str,
        dimension: int,
        metric: str = "cosine",
        index_type: str = "hnsw",
    ) -> None:
        """Create a vector collection."""
        await self._client._request(
            "POST",
            "/vectors/collections",
            body={"name": name, "dimension": dimension, "metric": metric, "index_type": index_type},
        )

    async def delete_collection(self, name: str) -> None:
        """Delete a vector collection."""
        await self._client._request("DELETE", f"/vectors/collections/{name}")

    async def get_collection_stats(self, name: str) -> VectorCollectionStats:
        """Get collection statistics."""
        response = await self._client._request("GET", f"/vectors/collections/{name}/stats")
        return VectorCollectionStats(
            name=response["name"],
            vector_count=response["vector_count"],
            dimension=response["dimension"],
            index_size=response["index_size"],
        )

    async def upsert(
        self,
        collection: str,
        vectors: List[Dict[str, Any]],
        namespace: Optional[str] = None,
    ) -> int:
        """Upsert vectors."""
        body: Dict[str, Any] = {"vectors": vectors}
        if namespace:
            body["namespace"] = namespace

        response = await self._client._request("POST", f"/vectors/{collection}/upsert", body=body)
        return response.get("upserted", 0)

    async def search(
        self,
        collection: str,
        vector: List[float],
        top_k: int = 10,
        namespace: Optional[str] = None,
        filter: Optional[Dict[str, Any]] = None,
        include_metadata: bool = True,
        include_vectors: bool = False,
        min_score: Optional[float] = None,
    ) -> List[SearchResult]:
        """Search for similar vectors."""
        body: Dict[str, Any] = {
            "vector": vector,
            "top_k": top_k,
            "include_metadata": include_metadata,
            "include_vectors": include_vectors,
        }
        if namespace:
            body["namespace"] = namespace
        if filter:
            body["filter"] = filter
        if min_score is not None:
            body["min_score"] = min_score

        response = await self._client._request("POST", f"/vectors/{collection}/search", body=body)
        return [
            SearchResult(
                id=r["id"],
                score=r["score"],
                vector=r.get("vector"),
                metadata=r.get("metadata"),
                content=r.get("content"),
            )
            for r in response.get("results", [])
        ]

    async def search_text(
        self,
        collection: str,
        text: str,
        top_k: int = 10,
        **kwargs,
    ) -> List[SearchResult]:
        """Search using text (requires embedding generation)."""
        body = {"text": text, "top_k": top_k, **kwargs}
        response = await self._client._request("POST", f"/vectors/{collection}/search/text", body=body)
        return [
            SearchResult(
                id=r["id"],
                score=r["score"],
                vector=r.get("vector"),
                metadata=r.get("metadata"),
                content=r.get("content"),
            )
            for r in response.get("results", [])
        ]

    async def delete(
        self,
        collection: str,
        ids: List[str],
        namespace: Optional[str] = None,
    ) -> int:
        """Delete vectors by ID."""
        body: Dict[str, Any] = {"ids": ids}
        if namespace:
            body["namespace"] = namespace

        response = await self._client._request("POST", f"/vectors/{collection}/delete", body=body)
        return response.get("deleted", 0)

    async def fetch(
        self,
        collection: str,
        ids: List[str],
        namespace: Optional[str] = None,
    ) -> List[VectorEntry]:
        """Fetch vectors by ID."""
        body: Dict[str, Any] = {"ids": ids}
        if namespace:
            body["namespace"] = namespace

        response = await self._client._request("POST", f"/vectors/{collection}/fetch", body=body)
        return [
            VectorEntry(
                id=v["id"],
                vector=v["vector"],
                metadata=v.get("metadata"),
                content=v.get("content"),
            )
            for v in response.get("vectors", [])
        ]


class TimeSeriesStore:
    """Time series store operations."""

    def __init__(self, client: "SynorDatabase"):
        self._client = client

    async def write(
        self,
        series: str,
        points: List[Dict[str, Any]],
        precision: str = "ms",
    ) -> int:
        """Write data points to a series."""
        body = {"points": points, "precision": precision}
        response = await self._client._request("POST", f"/timeseries/{series}/write", body=body)
        return response.get("written", 0)

    async def query(
        self,
        series: str,
        start: Union[int, datetime, str],
        end: Union[int, datetime, str],
        tags: Optional[Dict[str, str]] = None,
        aggregation: Optional[str] = None,
        interval: Optional[str] = None,
        fill: Optional[Union[str, float]] = None,
        limit: Optional[int] = None,
    ) -> TimeSeriesResult:
        """Query a time series."""
        body: Dict[str, Any] = {
            "range": {
                "start": self._format_time(start),
                "end": self._format_time(end),
            }
        }
        if tags:
            body["tags"] = tags
        if aggregation:
            body["aggregation"] = aggregation
        if interval:
            body["interval"] = interval
        if fill is not None:
            body["fill"] = fill
        if limit:
            body["limit"] = limit

        response = await self._client._request("POST", f"/timeseries/{series}/query", body=body)
        return TimeSeriesResult(
            series=response["series"],
            points=[
                DataPoint(
                    timestamp=p["timestamp"],
                    value=p["value"],
                    tags=p.get("tags"),
                )
                for p in response.get("points", [])
            ],
            statistics=response.get("statistics"),
        )

    async def delete(
        self,
        series: str,
        start: Union[int, datetime, str],
        end: Union[int, datetime, str],
        tags: Optional[Dict[str, str]] = None,
    ) -> int:
        """Delete data points in a range."""
        body: Dict[str, Any] = {
            "range": {
                "start": self._format_time(start),
                "end": self._format_time(end),
            }
        }
        if tags:
            body["tags"] = tags

        response = await self._client._request("POST", f"/timeseries/{series}/delete", body=body)
        return response.get("deleted", 0)

    async def get_series_info(self, series: str) -> SeriesInfo:
        """Get series information."""
        response = await self._client._request("GET", f"/timeseries/{series}/info")
        return SeriesInfo(
            name=response["name"],
            tags=response.get("tags", []),
            retention_days=response.get("retention_days", 0),
            point_count=response.get("point_count", 0),
        )

    async def list_series(self, prefix: Optional[str] = None) -> List[SeriesInfo]:
        """List all series."""
        params = f"?prefix={prefix}" if prefix else ""
        response = await self._client._request("GET", f"/timeseries{params}")
        return [
            SeriesInfo(
                name=s["name"],
                tags=s.get("tags", []),
                retention_days=s.get("retention_days", 0),
                point_count=s.get("point_count", 0),
            )
            for s in response.get("series", [])
        ]

    async def set_retention(self, series: str, retention_days: int) -> None:
        """Set retention policy for a series."""
        await self._client._request(
            "PUT", f"/timeseries/{series}/retention", body={"retention_days": retention_days}
        )

    def _format_time(self, t: Union[int, datetime, str]) -> Union[int, str]:
        if isinstance(t, datetime):
            return int(t.timestamp() * 1000)
        return t


class SynorDatabase:
    """
    Synor Database SDK Client.

    Multi-model database supporting Key-Value, Document, Vector, and Time Series data.
    """

    def __init__(
        self,
        api_key: str,
        endpoint: str = "https://database.synor.io/v1",
        network: Network = Network.MAINNET,
        timeout: float = 30.0,
        retries: int = 3,
        debug: bool = False,
    ):
        self._config = DatabaseConfig(
            api_key=api_key,
            endpoint=endpoint,
            network=network,
            timeout=timeout,
            retries=retries,
            debug=debug,
        )
        self._client = httpx.AsyncClient(
            base_url=endpoint,
            timeout=timeout,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "X-SDK-Version": "python/0.1.0",
            },
        )
        self._closed = False

        # Initialize stores
        self.kv = KeyValueStore(self)
        self.documents = DocumentStore(self)
        self.vectors = VectorStore(self)
        self.timeseries = TimeSeriesStore(self)

    async def get_stats(self) -> DatabaseStats:
        """Get database statistics."""
        response = await self._request("GET", "/stats")
        return DatabaseStats(
            kv_entries=response.get("kv_entries", 0),
            document_count=response.get("document_count", 0),
            vector_count=response.get("vector_count", 0),
            timeseries_points=response.get("timeseries_points", 0),
            storage_used=response.get("storage_used", 0),
            storage_limit=response.get("storage_limit", 0),
        )

    async def health_check(self) -> bool:
        """Health check."""
        try:
            response = await self._request("GET", "/health")
            return response.get("status") == "healthy"
        except Exception:
            return False

    async def close(self) -> None:
        """Close the client."""
        if not self._closed:
            await self._client.aclose()
            self._closed = True

    @property
    def is_closed(self) -> bool:
        """Check if the client is closed."""
        return self._closed

    async def __aenter__(self) -> "SynorDatabase":
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        await self.close()

    async def _request(
        self,
        method: str,
        path: str,
        body: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make an HTTP request with retry logic."""
        if self._closed:
            raise DatabaseError("Client has been closed")

        last_error: Optional[Exception] = None

        for attempt in range(self._config.retries):
            try:
                if method == "GET":
                    response = await self._client.get(path)
                elif method == "POST":
                    response = await self._client.post(path, json=body)
                elif method == "PUT":
                    response = await self._client.put(path, json=body)
                elif method == "PATCH":
                    response = await self._client.patch(path, json=body)
                elif method == "DELETE":
                    response = await self._client.delete(path)
                else:
                    raise ValueError(f"Unknown method: {method}")

                if response.status_code >= 400:
                    error_data = response.json() if response.content else {}
                    raise DatabaseError(
                        error_data.get("message") or error_data.get("error") or f"HTTP {response.status_code}",
                        error_data.get("code"),
                        response.status_code,
                    )

                return response.json() if response.content else {}

            except Exception as e:
                last_error = e
                if self._config.debug:
                    print(f"Attempt {attempt + 1} failed: {e}")

                if attempt < self._config.retries - 1:
                    import asyncio
                    await asyncio.sleep(attempt + 1)

        if last_error:
            raise last_error
        raise DatabaseError("Unknown error after retries")
