from abc import ABC, abstractmethod
from typing import Optional, Protocol

from mcard.model.card import MCard
from mcard.model.pagination import Page


class HandleHistoryEntry:
    """Represents a single entry in handle version history."""

    __slots__ = ("previous_hash", "changed_at")

    def __init__(self, previous_hash: str, changed_at: str) -> None:
        self.previous_hash = previous_hash
        self.changed_at = changed_at

    def to_dict(self) -> dict:
        return {"previous_hash": self.previous_hash, "changed_at": self.changed_at}


class DatabaseConnection(Protocol):
    """Protocol for database connections"""

    def connect(self) -> None: ...
    def disconnect(self) -> None: ...
    def commit(self) -> None: ...
    def rollback(self) -> None: ...


class StorageEngine(ABC):
    """Abstract base class for storage engines.

    Mirrors the TypeScript StorageEngine interface in mcard-js to ensure
    cross-language parity.  All card *and* handle operations are declared
    here so that every concrete engine is guaranteed to support them.
    """

    # ========== Card Operations ==========

    @abstractmethod
    def add(self, card: MCard) -> str:
        """Add a card and return its hash."""
        pass

    @abstractmethod
    def get(self, hash_value: str) -> Optional[MCard]:
        """Retrieve a card by its hash."""
        pass

    @abstractmethod
    def delete(self, hash_value: str) -> bool:
        """Delete a card by its hash."""
        pass

    @abstractmethod
    def get_page(self, page_number: int, page_size: int) -> Page:
        """Get a page of cards."""
        pass

    @abstractmethod
    def get_all_cards(self) -> Page:
        """Get all cards in a single page."""
        pass

    @abstractmethod
    def get_cards_by_page(self, page_number: int, page_size: int) -> Page:
        """Get a page of cards (canonical pagination method)."""
        pass

    @abstractmethod
    def search_by_string(self, search_string: str, page_number: int, page_size: int) -> Page:
        """Search cards by string in content, hash, or g_time."""
        pass

    @abstractmethod
    def search_by_content(self, search_string: str, page_number: int, page_size: int) -> Page:
        """Search cards by content pattern."""
        pass

    @abstractmethod
    def clear(self) -> None:
        """Remove all cards."""
        pass

    @abstractmethod
    def count(self) -> int:
        """Return total number of cards."""
        pass

    # ========== Handle Operations ==========

    @abstractmethod
    def ensure_handle_tables(self) -> None:
        """Ensure handle-related tables exist."""
        pass

    @abstractmethod
    def register_handle(self, handle: str, hash_value: str) -> bool:
        """Register a new handle pointing to a hash."""
        pass

    @abstractmethod
    def resolve_handle(self, handle: str) -> Optional[str]:
        """Resolve a handle to its current hash."""
        pass

    @abstractmethod
    def get_by_handle(self, handle: str) -> Optional[MCard]:
        """Get the MCard currently pointed to by a handle."""
        pass

    @abstractmethod
    def update_handle(self, handle: str, new_hash: str) -> str:
        """Update handle to point to new_hash; return previous hash."""
        pass

    @abstractmethod
    def get_handle_history(self, handle: str) -> list:
        """Get version history for a handle."""
        pass

    @abstractmethod
    def prune_handle_history(
        self, handle: str, older_than: Optional[str] = None, delete_all: bool = False
    ) -> int:
        """Prune version history for a handle."""
        pass

    # ========== Handle Management Operations ==========

    @abstractmethod
    def get_all_handles(self) -> list:
        """List all registered handles with their current hashes."""
        pass

    @abstractmethod
    def remove_handle(self, handle: str) -> None:
        """Remove a handle and all its history. Does NOT delete the card."""
        pass

    @abstractmethod
    def rename_handle(self, old_handle: str, new_handle: str) -> None:
        """Rename a handle atomically."""
        pass
