import json
import sqlite3
from pathlib import Path
from typing import Any

from pydantic import field_validator
from sinapsis_core.data_containers.model_types.llm_types import LLMMessage

from sinapsis_chat_history.helpers.schemas import (
    CHAT_HISTORY_INDEXED_COLUMNS,
    ChatHistoryColumn,
    ChatHistoryOrder,
    ChatHistoryOrderBy,
)
from sinapsis_chat_history.providers.base import BaseStorageProvider, DatabaseConfig


class SQLiteDatabaseConfig(DatabaseConfig):
    """Connection settings for SQLite chat history storage."""

    db_path: str = "chat_history.sqlite3"

    @field_validator("db_path")
    @classmethod
    def validate_db_path(cls, value: str) -> str:
        """Ensure the SQLite database path is not empty.

        Args:
            value (str): Candidate database path.

        Returns:
            str: Validated database path.
        """
        if not value.strip():
            raise ValueError("'db_path' must not be empty.")
        return value


class SQLiteStorageProvider(BaseStorageProvider):
    """SQLite-backed chat history provider."""

    config: SQLiteDatabaseConfig

    def initialize(self) -> None:
        """Create the SQLite database file, table, and indexes when missing."""
        db_file = Path(self.config.db_path)
        if db_file.parent != Path("."):
            db_file.parent.mkdir(parents=True, exist_ok=True)

        with sqlite3.connect(self.config.db_path) as conn:
            conn.execute(
                f"""
                CREATE TABLE IF NOT EXISTS {self.config.table} (
                    {ChatHistoryColumn.ID.value} TEXT PRIMARY KEY,
                    {ChatHistoryColumn.USER_ID.value} TEXT,
                    {ChatHistoryColumn.SESSION_ID.value} TEXT,
                    {ChatHistoryColumn.ROLE.value} TEXT NOT NULL,
                    {ChatHistoryColumn.CONTENT.value} TEXT NOT NULL,
                    {ChatHistoryColumn.METADATA.value} TEXT,
                    {ChatHistoryColumn.CREATED_AT.value} TEXT NOT NULL
                )
                """
            )
            for column in CHAT_HISTORY_INDEXED_COLUMNS:
                conn.execute(
                    f"CREATE INDEX IF NOT EXISTS {self.config.table}_{column.value}_idx "
                    f"ON {self.config.table} ({column.value})"
                )
            conn.commit()

    @staticmethod
    def _build_where_clause(filters: dict[str, Any]) -> tuple[str, list[Any]]:
        """Build a parameterized SQLite WHERE clause.

        Args:
            filters (dict[str, Any]): Validated filter dictionary.

        Returns:
            tuple[str, list[Any]]: SQLite WHERE clause and bound values.
        """
        clauses: list[str] = []
        params: list[Any] = []
        for key, value in filters.items():
            clauses.append(f"{key} = ?")
            params.append(value)
        if not clauses:
            return "TRUE", params
        return " AND ".join(clauses), params

    def save_messages(
        self,
        messages: list[dict[str, Any]],
        metadata: dict[str, Any],
        user_id: str | None = None,
        session_id: str | None = None,
    ) -> int:
        """Persist chat messages to SQLite.

        Args:
            messages (list[dict[str, Any]]): Messages to persist.
            metadata (dict[str, Any]): Shared metadata merged into every stored
                message.
            user_id (str | None): Optional user scope.
            session_id (str | None): Optional session scope.

        Returns:
            int: Number of inserted rows.
        """
        rows = self._normalize_messages(messages=messages, metadata=metadata, user_id=user_id, session_id=session_id)
        insert_sql = f"""
            INSERT INTO {self.config.table}
            (
                {ChatHistoryColumn.ID.value},
                {ChatHistoryColumn.USER_ID.value},
                {ChatHistoryColumn.SESSION_ID.value},
                {ChatHistoryColumn.ROLE.value},
                {ChatHistoryColumn.CONTENT.value},
                {ChatHistoryColumn.METADATA.value},
                {ChatHistoryColumn.CREATED_AT.value}
            )
            VALUES (?, ?, ?, ?, ?, ?, ?)
        """

        with sqlite3.connect(self.config.db_path) as conn:
            for row in rows:
                conn.execute(
                    insert_sql,
                    (
                        row[ChatHistoryColumn.ID.value],
                        user_id,
                        session_id,
                        row[ChatHistoryColumn.ROLE.value],
                        row[ChatHistoryColumn.CONTENT.value],
                        json.dumps(row[ChatHistoryColumn.METADATA.value])
                        if row[ChatHistoryColumn.METADATA.value]
                        else None,
                        row[ChatHistoryColumn.CREATED_AT.value].isoformat(),
                    ),
                )
            conn.commit()
        return len(rows)

    def fetch_messages(
        self,
        limit: int,
        offset: int,
        order: ChatHistoryOrder,
        order_by: ChatHistoryOrderBy,
        filters: dict[str, Any],
        user_id: str | None = None,
        session_id: str | None = None,
    ) -> list[LLMMessage]:
        """Load chat messages from SQLite.

        Args:
            limit (int): Maximum number of messages to return.
            offset (int): Number of messages to skip.
            order (ChatHistoryOrder): Sort direction.
            order_by (ChatHistoryOrderBy): Field used for ordering.
            filters (dict[str, Any]): Additional message-level filters.
            user_id (str | None): Optional user scope.
            session_id (str | None): Optional session scope.

        Returns:
            list[LLMMessage]: Normalized chat history messages.
        """
        self._validate_pagination(limit=limit, offset=offset)
        scoped_filters = self._build_scoped_filters(filters=filters, user_id=user_id, session_id=session_id)
        where_clause, params = self._build_where_clause(scoped_filters)
        select_columns = (
            f"{ChatHistoryColumn.ROLE.value}, {ChatHistoryColumn.CONTENT.value}, {ChatHistoryColumn.METADATA.value}"
        )
        if limit == -1:
            query = f"""
                SELECT {select_columns}
                FROM {self.config.table}
                WHERE {where_clause}
                ORDER BY {order_by} {order.upper()}, {ChatHistoryColumn.ID.value} {order.upper()}
                LIMIT -1 OFFSET ?
            """
            params.append(offset)
        else:
            query = f"""
                SELECT {select_columns}
                FROM {self.config.table}
                WHERE {where_clause}
                ORDER BY {order_by} {order.upper()}, {ChatHistoryColumn.ID.value} {order.upper()}
                LIMIT ? OFFSET ?
            """
            params.extend([limit, offset])

        with sqlite3.connect(self.config.db_path) as conn:
            rows = conn.execute(query, params).fetchall()

        messages_out: list[LLMMessage] = []
        for role, content, metadata_value in rows:
            payload: dict[str, Any] = {"role": role, "content": content}
            if metadata_value:
                payload["metadata"] = json.loads(metadata_value)
            messages_out.append(LLMMessage(**payload))
        return messages_out

    def remove_messages(
        self,
        filters: dict[str, Any],
        user_id: str | None = None,
        session_id: str | None = None,
    ) -> int:
        """Delete matching chat messages from SQLite.

        Args:
            filters (dict[str, Any]): Additional message-level filters.
            user_id (str | None): Optional user scope.
            session_id (str | None): Optional session scope.

        Returns:
            int: Number of deleted rows.
        """
        scoped_filters = self._build_scoped_filters(filters=filters, user_id=user_id, session_id=session_id)
        where_clause, params = self._build_where_clause(scoped_filters)
        query = f"DELETE FROM {self.config.table} WHERE {where_clause}"

        with sqlite3.connect(self.config.db_path) as conn:
            cursor = conn.execute(query, params)
            conn.commit()
        return cursor.rowcount

    def reset(self) -> None:
        """Drop and recreate the SQLite chat history table."""
        with sqlite3.connect(self.config.db_path) as conn:
            conn.execute(f"DROP TABLE IF EXISTS {self.config.table}")
            conn.commit()
        self.initialize()
