"""
SpyMonk-DB Client SDK.

Simple client interface for interacting with SpyMonk-DB.
"""

from pathlib import Path
from typing import Optional, Iterator, Tuple, List, Dict
import logging
import socket
import grpc

from spymonk_enterprise.time.hybrid_clock import HybridLogicalClock, Timestamp
from spymonk_enterprise.storage.mvcc import MVCCStore
from spymonk_enterprise.transaction.transaction import TransactionManager, Transaction
from spymonk_enterprise.network.grpc.gen import spymonk_pb2
from spymonk_enterprise.network.grpc.gen import spymonk_pb2_grpc

logger = logging.getLogger(__name__)


class SpyMonkClient:
    """
    SpyMonk-DB Client.

    Main interface for interacting with the database.
    Supports both local (embedded) and remote (gRPC) modes.

    Example (Embedded):
        >>> client = SpyMonkClient("/tmp/spymonk")
        >>> client.start()

    Example (Remote):
        >>> client = SpyMonkClient("spymonk://localhost:50051")
        >>> client.start()
    """

    def __init__(
        self,
        connection_string: str,
        node_id: Optional[str] = None
    ):
        """
        Initialize client.

        Args:
            connection_string: Data directory (embedded) or spymonk:// URL (remote)
            node_id: Unique node identifier (default: hostname)
        """
        self.node_id = node_id or socket.gethostname()
        
        if connection_string.startswith("spymonk://"):
            # Remote mode
            self.mode = "remote"
            self.target = connection_string.replace("spymonk://", "")
            self.channel = None
            self.stub = None
            logger.info(f"Initialized SpyMonk-DB remote client connecting to {self.target}")
        else:
            # Local embedded mode
            self.mode = "embedded"
            self.data_dir = Path(connection_string)
            self.data_dir.mkdir(parents=True, exist_ok=True)
            
            # Initialize components
            self.clock = HybridLogicalClock(self.node_id)
            self.store = MVCCStore(self.data_dir, self.clock)
            self.txn_manager = TransactionManager(self.clock, self.store)
            logger.info(f"Initialized SpyMonk-DB embedded client at {connection_string}")

    def start(self):
        """Start the client"""
        if self.mode == "remote":
            self.channel = grpc.insecure_channel(self.target)
            self.stub = spymonk_pb2_grpc.SpyMonkDBStub(self.channel)
            logger.info(f"Connected to remote SpyMonk-DB server at {self.target}")
        else:
            self.clock.start()
            logger.info("Started SpyMonk-DB embedded client")

    def stop(self):
        """Stop the client"""
        if self.mode == "remote":
            if self.channel:
                self.channel.close()
        else:
            self.clock.stop()
            self.store.close()
        logger.info(f"Stopped SpyMonk-DB {self.mode} client")

    # Internal helpers for gRPC conversion
    
    def _to_pb_timestamp(self, ts: Timestamp) -> spymonk_pb2.Timestamp:
        if ts is None: return spymonk_pb2.Timestamp()
        return spymonk_pb2.Timestamp(
            physical=ts.physical,
            logical=ts.logical,
            uncertainty_ns=ts.uncertainty_ns
        )

    # Simple KV operations (auto-commit)

    def put(self, key: bytes, value: bytes) -> Optional[Timestamp]:
        """Write key-value pair (auto-commit)."""
        if self.mode == "remote":
            request = spymonk_pb2.PutRequest(key=key, value=value)
            response = self.stub.Put(request)
            return response.timestamp if response.success else None
        else:
            return self.store.put(key, value)

    def get(self, key: bytes) -> Optional[bytes]:
        """Read value for key."""
        if self.mode == "remote":
            request = spymonk_pb2.GetRequest(key=key)
            response = self.stub.Get(request)
            return response.value if response.found else None
        else:
            return self.store.get(key)

    def delete(self, key: bytes) -> Optional[Timestamp]:
        """Delete key."""
        if self.mode == "remote":
            request = spymonk_pb2.DeleteRequest(key=key)
            response = self.stub.Delete(request)
            return response.timestamp if response.success else None
        else:
            return self.store.delete(key)

    def scan(
        self,
        start_key: Optional[bytes] = None,
        end_key: Optional[bytes] = None
    ) -> Iterator[Tuple[bytes, bytes]]:
        """Range scan."""
        if self.mode == "remote":
            request = spymonk_pb2.ScanRequest(
                start_key=start_key or b"",
                end_key=end_key or b""
            )
            for response in self.stub.Scan(request):
                yield response.key, response.value
        else:
            yield from self.store.scan(start_key, end_key)

    # SQL Execution
    
    def execute_sql(self, sql: str) -> List[Dict[str, bytes]]:
        """Execute SQL query."""
        if self.mode == "remote":
            request = spymonk_pb2.ExecuteSQLRequest(sql=sql)
            response = self.stub.ExecuteSQL(request)
            results = []
            for row in response.rows:
                results.append(row.columns)
            return results
        else:
            # TODO: Local SQL execution if embedded
            raise NotImplementedError("Local SQL execution not yet implemented in client")

    # Transaction API

    def begin_transaction(self, read_only: bool = False) -> 'RemoteTransaction':
        """Begin a new transaction."""
        if self.mode == "remote":
            request = spymonk_pb2.BeginTransactionRequest(read_only=read_only)
            response = self.stub.BeginTransaction(request)
            return RemoteTransaction(self, response.transaction_id, read_only)
        else:
            return self.txn_manager.begin(read_only=read_only)


class RemoteTransaction:
    """Proxy for a transaction on a remote server."""
    
    def __init__(self, client: SpyMonkClient, txn_id: str, read_only: bool):
        self.client = client
        self.txn_id = txn_id
        self.read_only = read_only
        self.active = True

    def get(self, key: bytes) -> Optional[bytes]:
        request = spymonk_pb2.GetRequest(key=key, transaction_id=self.txn_id)
        response = self.client.stub.Get(request)
        return response.value if response.found else None

    def put(self, key: bytes, value: bytes):
        if self.read_only: raise Exception("Cannot write in read-only transaction")
        request = spymonk_pb2.PutRequest(key=key, value=value, transaction_id=self.txn_id)
        self.client.stub.Put(request)

    def delete(self, key: bytes):
        if self.read_only: raise Exception("Cannot delete in read-only transaction")
        request = spymonk_pb2.DeleteRequest(key=key, transaction_id=self.txn_id)
        self.client.stub.Delete(request)

    def commit(self) -> bool:
        request = spymonk_pb2.CommitTransactionRequest(transaction_id=self.txn_id)
        response = self.client.stub.CommitTransaction(request)
        self.active = False
        return response.success

    def abort(self):
        request = spymonk_pb2.AbortTransactionRequest(transaction_id=self.txn_id)
        self.client.stub.AbortTransaction(request)
        self.active = False

    # Context manager support

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

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