"""Shallow persistence savers for LangGraph SQLite checkpointing.

Provides shallow (single-checkpoint-per-thread) variants of the LangGraph
SQLite savers that automatically delete old checkpoints and writes for the
same thread before each write, keeping only the latest state.

This prevents unbounded growth of ``checkpoints.db`` while preserving
multi-turn conversation continuity.

Implements:
- ``AsyncShallowSqliteSaver`` — async shallow variant
- ``ShallowSqliteSaver`` — sync shallow variant

Usage is transparent through the existing checkpointer factory when
``sqlite_mode: shallow`` is set in ``config.yaml``.
"""

from __future__ import annotations

import logging
from typing import Any

from langchain_core.runnables import RunnableConfig
from langgraph.checkpoint.base import ChannelVersions, Checkpoint, CheckpointMetadata

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Async shallow saver
# ---------------------------------------------------------------------------


class AsyncShallowSqliteSaver:
    """Async SQLite checkpointer that keeps only the latest checkpoint per thread.

    Extends :class:`langgraph.checkpoint.sqlite.aio.AsyncSqliteSaver` and
    overrides :meth:`aput` to delete all existing checkpoints and writes for
    the same ``thread_id`` before inserting the new one.

    Each conversation thread stores exactly one checkpoint at any time,
    preventing unbounded database growth.
    """

    def __init_subclass__(cls, **kwargs: Any) -> None:
        # Allow extension by subclasses without forcing late-bound import here.
        # The concrete class is built below via the _make_async_shallow factory.
        super().__init_subclass__(**kwargs)


def _make_async_shallow_saver() -> type:
    """Build and return the ``AsyncShallowSqliteSaver`` class.

    Import is deferred so that the module is importable even when
    ``langgraph-checkpoint-sqlite`` is not installed.
    """
    from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver

    class AsyncShallowSqliteSaver(AsyncSqliteSaver):
        async def aput(
            self,
            config: RunnableConfig,
            checkpoint: Checkpoint,
            metadata: CheckpointMetadata,
            new_versions: ChannelVersions,
        ) -> RunnableConfig:
            """Delete old checkpoints/writes for this thread, then save the new one."""

            thread_id = config["configurable"]["thread_id"]
            await self.setup()

            # Delete all existing checkpoints and writes for this thread
            # before inserting the new checkpoint — keeps only the latest.
            async with self.lock:
                await self.conn.execute(
                    "DELETE FROM checkpoints WHERE thread_id = ?",
                    (str(thread_id),),
                )
                await self.conn.execute(
                    "DELETE FROM writes WHERE thread_id = ?",
                    (str(thread_id),),
                )
                await self.conn.commit()

            return await super().aput(config, checkpoint, metadata, new_versions)

    return AsyncShallowSqliteSaver


# ---------------------------------------------------------------------------
# Sync shallow saver
# ---------------------------------------------------------------------------


def _make_sync_shallow_saver() -> type:
    """Build and return the ``ShallowSqliteSaver`` class.

    Import is deferred so that the module is importable even when
    ``langgraph-checkpoint-sqlite`` is not installed.
    """
    from langgraph.checkpoint.sqlite import SqliteSaver

    class ShallowSqliteSaver(SqliteSaver):
        def put(
            self,
            config: RunnableConfig,
            checkpoint: Checkpoint,
            metadata: CheckpointMetadata,
            new_versions: ChannelVersions,
        ) -> RunnableConfig:
            """Delete old checkpoints/writes for this thread, then save the new one."""

            thread_id = config["configurable"]["thread_id"]

            # Delete all existing checkpoints and writes for this thread
            # before inserting the new checkpoint — keeps only the latest.
            with self.cursor() as cur:
                cur.execute(
                    "DELETE FROM checkpoints WHERE thread_id = ?",
                    (str(thread_id),),
                )
                cur.execute(
                    "DELETE FROM writes WHERE thread_id = ?",
                    (str(thread_id),),
                )

            return super().put(config, checkpoint, metadata, new_versions)

    return ShallowSqliteSaver
