"""The sqlite_cache module.

This module provides the following classes:
- SQLiteCache
"""

__all__ = ["SQLiteCache"]

import sqlite3
from collections.abc import Generator
from contextlib import contextmanager
from datetime import datetime, timezone
from pathlib import Path

from seagrin import get_cache_root
from seagrin.cache.schemas import CacheData


class SQLiteCache:
    """The SQLiteCache object to cache responses from Metron.

    Args:
        path: Path to database.
    """

    def __init__(self, path: Path) -> None:
        self._db_path = path or (get_cache_root() / "cache.sqlite")
        self.create_table()

    @contextmanager
    def _connect(self) -> Generator[sqlite3.Connection]:
        conn = None
        try:
            conn = sqlite3.connect(self._db_path)
            conn.row_factory = sqlite3.Row
            conn.execute("PRAGMA foreign_keys = ON")
            yield conn
        finally:
            if conn:
                conn.close()

    def create_table(self) -> None:
        """Create the cache table if it doesn't exist."""
        query = """
        CREATE TABLE IF NOT EXISTS queries (
            url TEXT PRIMARY KEY,
            response TEXT NOT NULL,
            `timestamp` DATETIME NOT NULL,
            last_modified DATETIME NOT NULL
        );
        """
        with self._connect() as conn:
            conn.execute(query)
            conn.commit()

    def select(self, url: str) -> CacheData | None:
        """Retrieve a cached entry by URL.

        Args:
            url: The URL used as the cache key.

        Returns:
            A `CacheData` instance if a matching record exists, otherwise `None`.
        """
        query = """
        SELECT url, response, `timestamp`, last_modified
        FROM queries
        WHERE url = ?;
        """
        with self._connect() as conn:
            if row := conn.execute(query, (url,)).fetchone():
                return CacheData(
                    url=row["url"],
                    response=row["response"],
                    timestamp=datetime.fromisoformat(row["timestamp"]),
                    last_modified=datetime.fromisoformat(row["last_modified"]),
                )
            return None

    def insert(
        self,
        url: str,
        response: str,
        timestamp: datetime | None = None,
        last_modified: datetime | None = None,
    ) -> None:
        """Insert or update a cached response.

        If a record with the same *url* already exists it is overwritten (upsert semantics).

        Args:
            url: The URL used as the cache key.
            response: The serialised response body to store.
            timestamp: The time the response was fetched.
                Defaults to the current UTC datetime.
            last_modified: The `Last-Modified` value reported by the server.
                Defaults to the current UTC datetime.
        """
        query = """
        INSERT INTO queries (url, response, `timestamp`, last_modified)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(url) DO UPDATE SET
            response = excluded.response,
            `timestamp` = excluded.`timestamp`,
            last_modified = excluded.last_modified;
        """
        with self._connect() as conn:
            conn.execute(
                query,
                (
                    url,
                    response,
                    (timestamp or datetime.now(tz=timezone.utc)).isoformat(),
                    (last_modified or datetime.now(tz=timezone.utc)).isoformat(),
                ),
            )
            conn.commit()

    def delete(self, url: str) -> None:
        """Delete a cached entry by URL.

        Args:
            url: The URL of the entry to remove.
        """
        query = """
        DELETE FROM queries
        WHERE url = ?;
        """
        with self._connect() as conn:
            conn.execute(query, (url,))
