"""
AbstractSqlEngine — Base class for SQL-backed StorageEngine implementations.

Provides concrete implementations of all handle operations using two abstract
primitives that each SQL engine must implement:

    _query_rows(sql, params)  — Run a SELECT and return list of dicts
    _exec_sql(sql, params)    — Run a write statement and return rows-changed

SQL uses ``?`` as the canonical placeholder.  Each engine is responsible
for translating to its native syntax (e.g. DuckDB's ``$1, $2, ...``).

Card operations (add, get, delete, get_page, search, etc.) remain abstract
because they involve engine-specific cursor patterns and exception types.
"""

import logging
from abc import abstractmethod
from datetime import datetime, timezone

from mcard.engine.base import StorageEngine
from mcard.model.card import MCard, MCardFromData
from mcard.model.pagination import Page

logger = logging.getLogger(__name__)


class AbstractSqlEngine(StorageEngine):
    """Intermediate base class providing concrete handle operations.

    Subclasses must implement ``_query_rows``, ``_exec_sql``, and
    ``_ensure_handle_tables``.  All handle methods are written once here
    and inherited by both SQLiteEngine and DuckDBEngine.
    """

    # =====================================================================
    # Abstract primitives — each SQL engine implements these
    # =====================================================================

    @abstractmethod
    def _query_rows(self, sql: str, params: tuple = ()) -> list[dict]:
        """Run a SELECT query and return a list of row dicts.

        Each dict maps column name -> value.  SQL uses ``?`` placeholders.
        """
        ...

    @abstractmethod
    def _exec_sql(self, sql: str, params: tuple = ()) -> int:
        """Run a write statement (INSERT/UPDATE/DELETE) and return rows-changed.

        SQL uses ``?`` placeholders.
        """
        ...

    @abstractmethod
    def _ensure_handle_tables(self) -> None:
        """Ensure handle_registry and handle_history tables exist."""
        ...

    # =====================================================================
    # Shared: _row_to_card (identical in both engines)
    # =====================================================================

    @staticmethod
    def _row_to_card(content, hash_value: str, g_time: str) -> MCard:
        """Convert a database row into an MCard instance."""
        if not isinstance(content, bytes):
            logger.warning(
                f"Content from database is not bytes but {type(content)}: "
                f"{str(content)[:20]}..."
            )
            content_bytes = (
                content.encode("utf-8") if isinstance(content, str) else bytes(content)
            )
        else:
            content_bytes = content
        return MCardFromData(content_bytes, hash_value, g_time)

    # =====================================================================
    # Shared: get_all_cards, get_page delegate, clear
    # =====================================================================

    def get_all_cards(self) -> Page:
        """Get all cards in a single page."""
        rows = self._query_rows("SELECT content, hash, g_time FROM card")
        items = [self._row_to_card(r["content"], r["hash"], r["g_time"]) for r in rows]
        return Page(
            items=items,
            total_items=len(items),
            page_number=1,
            page_size=len(items) if items else 1,
            has_next=False,
            has_previous=False,
            total_pages=1,
        )

    def get_page(self, page_number: int = 1, page_size: int = 10) -> Page:
        """Delegate to get_cards_by_page."""
        return self.get_cards_by_page(page_number, page_size)

    def clear(self) -> None:
        """Remove all cards."""
        self._exec_sql("DELETE FROM card")

    # =====================================================================
    # Concrete: Handle Operations
    # =====================================================================

    def ensure_handle_tables(self) -> None:
        self._ensure_handle_tables()

    def register_handle(self, handle: str, hash_value: str) -> bool:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        validated = validate_handle(handle)
        now = datetime.now(timezone.utc).isoformat()

        # Check for duplicate
        existing = self._query_rows(
            "SELECT handle FROM handle_registry WHERE handle = ?",
            (validated,),
        )
        if existing:
            raise ValueError(f"Handle '{validated}' already exists.")

        self._exec_sql(
            "INSERT INTO handle_registry (handle, current_hash, created_at, updated_at) "
            "VALUES (?, ?, ?, ?)",
            (validated, hash_value, now, now),
        )
        logger.info(f"Registered handle '{validated}' -> {hash_value[:8]}...")
        return True

    def resolve_handle(self, handle: str) -> str | None:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        validated = validate_handle(handle)
        rows = self._query_rows(
            "SELECT current_hash FROM handle_registry WHERE handle = ?",
            (validated,),
        )
        return rows[0]["current_hash"] if rows else None

    def get_by_handle(self, handle: str) -> MCard | None:
        hash_value = self.resolve_handle(handle)
        if hash_value:
            return self.get(hash_value)
        return None

    def update_handle(self, handle: str, new_hash: str) -> str:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        validated = validate_handle(handle)
        now = datetime.now(timezone.utc).isoformat()

        rows = self._query_rows(
            "SELECT current_hash FROM handle_registry WHERE handle = ?",
            (validated,),
        )
        if not rows:
            raise ValueError(f"Handle '{validated}' not found.")

        previous_hash = rows[0]["current_hash"]

        # Record history
        self._exec_sql(
            "INSERT INTO handle_history (handle, previous_hash, changed_at) "
            "VALUES (?, ?, ?)",
            (validated, previous_hash, now),
        )

        # Update handle
        self._exec_sql(
            "UPDATE handle_registry SET current_hash = ?, updated_at = ? WHERE handle = ?",
            (new_hash, now, validated),
        )

        logger.info(
            f"Updated handle '{validated}': {previous_hash[:8]}... -> {new_hash[:8]}..."
        )
        return previous_hash

    def get_handle_history(self, handle: str) -> list:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        validated = validate_handle(handle)
        rows = self._query_rows(
            "SELECT previous_hash, changed_at FROM handle_history "
            "WHERE handle = ? ORDER BY id DESC",
            (validated,),
        )
        return [
            {"previous_hash": r["previous_hash"], "changed_at": r["changed_at"]}
            for r in rows
        ]

    def prune_handle_history(
        self, handle: str, older_than: str | None = None, delete_all: bool = False
    ) -> int:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        validated = validate_handle(handle)

        if delete_all:
            return self._exec_sql(
                "DELETE FROM handle_history WHERE handle = ?", (validated,)
            )
        elif older_than:
            return self._exec_sql(
                "DELETE FROM handle_history WHERE handle = ? AND changed_at < ?",
                (validated, older_than),
            )
        return 0

    # =====================================================================
    # Concrete: Handle Management Operations
    # =====================================================================

    def get_all_handles(self) -> list:
        self._ensure_handle_tables()
        rows = self._query_rows("SELECT handle, current_hash FROM handle_registry")
        return [{"handle": r["handle"], "hash": r["current_hash"]} for r in rows]

    def remove_handle(self, handle: str) -> None:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        validated = validate_handle(handle)
        self._exec_sql("DELETE FROM handle_history WHERE handle = ?", (validated,))
        self._exec_sql("DELETE FROM handle_registry WHERE handle = ?", (validated,))
        logger.info(f"Removed handle '{validated}' and its history.")

    def rename_handle(self, old_handle: str, new_handle: str) -> None:
        from mcard.model.handle import validate_handle

        self._ensure_handle_tables()
        old_validated = validate_handle(old_handle)
        new_validated = validate_handle(new_handle)

        if old_validated == new_validated:
            return

        # Check old exists
        old_rows = self._query_rows(
            "SELECT handle, current_hash, created_at, updated_at "
            "FROM handle_registry WHERE handle = ?",
            (old_validated,),
        )
        if not old_rows:
            raise ValueError(f"Handle '{old_validated}' not found.")

        # Check new doesn't exist
        new_rows = self._query_rows(
            "SELECT handle FROM handle_registry WHERE handle = ?",
            (new_validated,),
        )
        if new_rows:
            raise ValueError(f"Handle '{new_validated}' already exists.")

        # FK-safe rename: INSERT new → UPDATE history → DELETE old.
        # handle_history.handle FK-references handle_registry.handle, so we
        # cannot simply UPDATE either table first without violating the constraint.
        old_row = old_rows[0]
        self._exec_sql(
            "INSERT INTO handle_registry (handle, current_hash, created_at, updated_at) "
            "VALUES (?, ?, ?, ?)",
            (
                new_validated,
                old_row["current_hash"],
                old_row["created_at"],
                old_row["updated_at"],
            ),
        )
        self._exec_sql(
            "UPDATE handle_history SET handle = ? WHERE handle = ?",
            (new_validated, old_validated),
        )
        self._exec_sql(
            "DELETE FROM handle_registry WHERE handle = ?",
            (old_validated,),
        )
        logger.info(f"Renamed handle '{old_validated}' -> '{new_validated}'.")
