"""
This module implements the BeaverClient, a drop-in replacement for the
BeaverDB class that interacts with a remote BeaverDB server over HTTP.

This implementation relies on the 'httpx' library.
"""

import threading
from typing import Generic, Type, TypeVar, Optional, Any
import httpx
from .types import JsonSerializable
from .collections import Document

# --- Base Remote Manager ---

class RemoteManager:
    """Base class for all remote managers, holding the HTTP client."""
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type | None = None
    ):
        self._client = client
        self._name = name
        self._model = model
        self._validate_model(model)

    def _validate_model(self, model: Type | None):
        """Helper to validate the model, mirroring BeaverDB.core"""
        if model and not isinstance(model, JsonSerializable):
            # This check might need to be refined if Pydantic isn't a direct dependency
            pass

# --- Stub Remote Manager Implementations ---

class RemoteDictManager[T](RemoteManager):
    """
    Manages a remote dictionary. All methods will make HTTP requests.
    (This is a skeleton implementation)
    """
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type[T] | None = None
    ):
        super().__init__(client, name, model)
        # Placeholder for manager-level locking, mirroring local implementation
        self._lock = RemoteLockManager(client, f"__lock__dict__{name}", None)

    def __setitem__(self, key: str, value: T):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def __getitem__(self, key: str) -> T:
        raise NotImplementedError("This method will be implemented in a future milestone.")

    # ... other dict methods (get, __delitem__, __len__, items, etc.) ...


class RemoteListManager[T](RemoteManager):
    """
    Manages a remote list. All methods will make HTTP requests.
    (This is a skeleton implementation)
    """
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type[T] | None = None
    ):
        super().__init__(client, name, model)
        self._lock = RemoteLockManager(client, f"__lock__list__{name}", None)

    def push(self, value: T):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    # ... other list methods (pop, __getitem__, __setitem__, etc.) ...


class RemoteQueueManager[T](RemoteManager):
    """
    Manages a remote priority queue. All methods will make HTTP requests.
    (This is a skeleton implementation)
    """
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type[T] | None = None
    ):
        super().__init__(client, name, model)
        self._lock = RemoteLockManager(client, f"__lock__queue__{name}", None)

    def put(self, data: T, priority: float):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def get(self, block: bool = True, timeout: float | None = None):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    # ... other queue methods (peek, __len__, etc.) ...


class RemoteCollectionManager[D](RemoteManager):
    """
    Manages a remote collection. All methods will make HTTP requests.
    (This is a skeleton implementation)
    """
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type[D] | None = None
    ):
        super().__init__(client, name, model)
        self._lock = RemoteLockManager(client, f"__lock__collection__{name}", None)

    def index(self, document: D, *, fts: bool | list[str] = True, fuzzy: bool = False):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def search(self, vector: list[float], top_k: int = 10):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    # ... other collection methods (drop, match, connect, walk, etc.) ...


class RemoteChannelManager[T](RemoteManager):
    """
    Manages a remote pub/sub channel.
    (This is a skeleton implementation)
    """
    def publish(self, payload: T):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def subscribe(self):
        raise NotImplementedError("This method will be implemented in a future milestone.")


class RemoteBlobManager[M](RemoteManager):
    """
    Manages a remote blob store. All methods will make HTTP requests.
    (This is a skeleton implementation)
    """
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type[M] | None = None
    ):
        super().__init__(client, name, model)
        self._lock = RemoteLockManager(client, f"__lock__blob__{name}", None)

    def put(self, key: str, data: bytes, metadata: Optional[M] = None):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def get(self, key: str):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    # ... other blob methods (delete, __len__, etc.) ...


class RemoteLogManager[T](RemoteManager):
    """
    Manages a remote time-indexed log.
    (This is a skeleton implementation)
    """
    def log(self, data: T, timestamp: Any | None = None): # Using Any for datetime
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def range(self, start: Any, end: Any): # Using Any for datetime
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def live(self, window: Any, period: Any, aggregator: Any): # Using Any for timedelta/callable
        raise NotImplementedError("This method will be implemented in a future milestone.")


class RemoteLockManager(RemoteManager):
    """
    Manages a remote inter-process lock.
    (This is a skeleton implementation)
    """
    def __init__(
        self,
        client: httpx.Client,
        name: str,
        model: Type | None = None,
        timeout: float | None = None,
        lock_ttl: float = 60.0,
        poll_interval: float = 0.1,
    ):
        super().__init__(client, name, model)
        self._timeout = timeout
        self._lock_ttl = lock_ttl
        self._poll_interval = poll_interval

    def acquire(self):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def release(self):
        raise NotImplementedError("This method will be implemented in a future milestone.")

    def __enter__(self):
        self.acquire()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.release()


# --- The Main Client Class ---

class BeaverClient:
    """
    A drop-in client for a remote BeaverDB server.

    This class provides the same factory methods as the local BeaverDB class,
    but all operations are performed over HTTP/WebSockets against a
    server running 'beaver serve'.
    """

    def __init__(self, base_url: str, **httpx_args):
        """
        Initializes the client.

        Args:
            base_url: The base URL of the BeaverDB server (e.g., "http://127.0.0.1:8000").
            **httpx_args: Additional keyword arguments to pass to the httpx.Client
                          (e.g., headers, timeouts).
        """
        self._client = httpx.Client(base_url=base_url, **httpx_args)

        # Singleton managers for collections and channels, just like in BeaverDB.core
        self._collections: dict[str, RemoteCollectionManager] = {}
        self._collections_lock = threading.Lock()
        self._channels: dict[str, RemoteChannelManager] = {}
        self._channels_lock = threading.Lock()

    def close(self):
        """Closes the underlying HTTP client session."""
        self._client.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def dict[T](self, name: str, model: type[T] | None = None) -> RemoteDictManager[T]:
        """
        Returns a wrapper for interacting with a remote named dictionary.
        If model is defined, it should be a type used for automatic (de)serialization.
        """
        if not isinstance(name, str) or not name:
            raise TypeError("Dictionary name must be a non-empty string.")

        return RemoteDictManager(self._client, name, model)

    def list[T](self, name: str, model: type[T] | None = None) -> RemoteListManager[T]:
        """
        Returns a wrapper for interacting with a remote named list.
        If model is defined, it should be a type used for automatic (de)serialization.
        """
        if not isinstance(name, str) or not name:
            raise TypeError("List name must be a non-empty string.")

        return RemoteListManager(self._client, name, model)

    def queue[T](self, name: str, model: type[T] | None = None) -> RemoteQueueManager[T]:
        """
        Returns a wrapper for interacting with a remote persistent priority queue.
        If model is defined, it should be a type used for automatic (de)serialization.
        """
        if not isinstance(name, str) or not name:
            raise TypeError("Queue name must be a non-empty string.")

        return RemoteQueueManager(self._client, name, model)

    def collection[D: Document](
        self, name: str, model: Type[D] | None = None
    ) -> RemoteCollectionManager[D]:
        """
        Returns a singleton wrapper for a remote document collection.
        """
        if not isinstance(name, str) or not name:
            raise TypeError("Collection name must be a non-empty string.")

        with self._collections_lock:
            if name not in self._collections:
                self._collections[name] = RemoteCollectionManager(self._client, name, model)
            return self._collections[name] # type: ignore

    def channel[T](self, name: str, model: type[T] | None = None) -> RemoteChannelManager[T]:
        """
        Returns a singleton wrapper for a remote pub/sub channel.
        """
        if not isinstance(name, str) or not name:
            raise ValueError("Channel name must be a non-empty string.")

        with self._channels_lock:
            if name not in self._channels:
                self._channels[name] = RemoteChannelManager(self._client, name, model)
            return self._channels[name]

    def blobs[M](self, name: str, model: type[M] | None = None) -> RemoteBlobManager[M]:
        """Returns a wrapper for interacting with a remote blob store."""
        if not isinstance(name, str) or not name:
            raise TypeError("Blob store name must be a non-empty string.")

        return RemoteBlobManager(self._client, name, model)

    def log[T](self, name: str, model: type[T] | None = None) -> RemoteLogManager[T]:
        """
        Returns a wrapper for interacting with a remote, time-indexed log.
        If model is defined, it should be a type used for automatic (de)serialization.
        """
        if not isinstance(name, str) or not name:
            raise TypeError("Log name must be a non-empty string.")

        return RemoteLogManager(self._client, name, model)

    def lock(
        self,
        name: str,
        timeout: float | None = None,
        lock_ttl: float = 60.0,
        poll_interval: float = 0.1,
    ) -> RemoteLockManager:
        """
        Returns a wrapper for a remote inter-process lock.
        """
        return RemoteLockManager(
            self._client,
            name,
            model=None,
            timeout=timeout,
            lock_ttl=lock_ttl,
            poll_interval=poll_interval,
        )

    # --- Async API ---

    def as_async(self) -> "AsyncBeaverClient":
        """
        Returns an async-compatible version of the client.
        (This is a skeleton implementation)
        """
        raise NotImplementedError("AsyncBeaverClient will be implemented in a future milestone.")


class AsyncBeaverClient:
    """
    An async-compatible, drop-in client for a remote BeaverDB server.
    (This is a skeleton implementation)
    """
    def __init__(self, base_url: str, **httpx_args):
        self._client = httpx.AsyncClient(base_url=base_url, **httpx_args)
        # ... async-compatible locks and manager caches ...

    async def close(self):
        await self._client.aclose()
