from __future__ import annotations

from pathlib import Path
import sqlite3
import time
from typing import Any

from .codec import (
    DEFAULT_COMPATIBILITY_VERSION,
    DEFAULT_SCHEMA_VERSION,
    decode_state,
    encode_state,
)


class SQLiteStateBackend:
    def __init__(
        self,
        path: str | Path,
        *,
        compatibility_version: int = DEFAULT_COMPATIBILITY_VERSION,
        schema_version: int = DEFAULT_SCHEMA_VERSION,
        now_fn=time.time,
    ) -> None:
        resolved_path = Path(path).expanduser()
        resolved_path.parent.mkdir(parents=True, exist_ok=True)
        self._conn = sqlite3.connect(str(resolved_path), check_same_thread=False)
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute("PRAGMA synchronous=NORMAL")
        self._compatibility_version = int(compatibility_version)
        self._schema_version = int(schema_version)
        self._now_fn = now_fn
        self._ensure_schema()

    def get(self, namespace: str, key: str) -> Any | None:
        self._prune_expired()
        cursor = self._conn.execute(
            """
            SELECT value_blob
            FROM state_entries
            WHERE namespace = ? AND key = ?
            """,
            (namespace, key),
        )
        row = cursor.fetchone()
        if row is None:
            return None
        return decode_state(
            row[0],
            expected_compatibility_version=self._compatibility_version,
        )

    def put(
        self,
        namespace: str,
        key: str,
        value: Any,
        ttl_seconds: float | None = None,
    ) -> None:
        expires_at = self._resolve_expiry(ttl_seconds)
        value_blob = encode_state(
            value,
            compatibility_version=self._compatibility_version,
            schema_version=self._schema_version,
        )
        self._conn.execute(
            """
            INSERT INTO state_entries(namespace, key, value_blob, expires_at)
            VALUES(?, ?, ?, ?)
            ON CONFLICT(namespace, key) DO UPDATE SET
              value_blob = excluded.value_blob,
              expires_at = excluded.expires_at
            """,
            (namespace, key, value_blob, expires_at),
        )
        self._conn.commit()
        self._prune_expired()

    def delete(self, namespace: str, key: str) -> None:
        self._conn.execute(
            """
            DELETE FROM state_entries
            WHERE namespace = ? AND key = ?
            """,
            (namespace, key),
        )
        self._conn.commit()

    def scan(
        self,
        namespace: str,
        *,
        prefix: str | None = None,
        limit: int | None = None,
    ) -> list[tuple[str, Any]]:
        self._prune_expired()
        query = """
            SELECT key, value_blob
            FROM state_entries
            WHERE namespace = ?
        """
        params: list[Any] = [namespace]
        if prefix is not None:
            query += " AND key LIKE ?"
            params.append(f"{prefix}%")
        query += " ORDER BY key ASC"
        if limit is not None:
            query += " LIMIT ?"
            params.append(max(0, int(limit)))

        rows = self._conn.execute(query, tuple(params)).fetchall()
        return [
            (
                row[0],
                decode_state(
                    row[1],
                    expected_compatibility_version=self._compatibility_version,
                ),
            )
            for row in rows
        ]

    def close(self) -> None:
        self._conn.close()

    def _ensure_schema(self) -> None:
        self._conn.execute(
            """
            CREATE TABLE IF NOT EXISTS state_entries (
                namespace TEXT NOT NULL,
                key TEXT NOT NULL,
                value_blob BLOB NOT NULL,
                expires_at REAL NULL,
                PRIMARY KEY(namespace, key)
            )
            """
        )
        self._conn.execute(
            """
            CREATE INDEX IF NOT EXISTS idx_state_entries_namespace_key
            ON state_entries(namespace, key)
            """
        )
        self._conn.execute(
            """
            CREATE INDEX IF NOT EXISTS idx_state_entries_expires_at
            ON state_entries(expires_at)
            """
        )
        self._conn.commit()

    def _prune_expired(self) -> None:
        now = self._now_fn()
        self._conn.execute(
            """
            DELETE FROM state_entries
            WHERE expires_at IS NOT NULL AND expires_at <= ?
            """,
            (now,),
        )
        self._conn.commit()

    def _resolve_expiry(self, ttl_seconds: float | None) -> float | None:
        if ttl_seconds is None:
            return None
        ttl = float(ttl_seconds)
        if ttl <= 0:
            return self._now_fn()
        return self._now_fn() + ttl


__all__ = ["SQLiteStateBackend"]
