import sqlite3
import json
import aiosqlite
from pathlib import Path
from datetime import datetime
from .exceptions import ChatException
from .logger import get_logger

logger = get_logger(f"dracula.{__name__}")

DEFAULT_DB_PATH = Path.home() / ".dracula" / "memory.db"


class Memory:
    """Manages conversational state and long-term persistence using SQLite.

    The Memory class handles the storage, retrieval, and maintenance of chat
    history between the user and the Gemini model. By utilizing an SQLite
    backend, it ensures data integrity, prevents memory exhaustion (bloat)
    in long-running sessions, and provides structured access to historical
    context.

    Attributes:
        db_path (Path): The file system path to the SQLite database.
        max_messages (int): The rolling limit for the number of messages
            retained in active context.
    """

    def __init__(self, db_path: str | Path = None, max_messages: int = 10):
        """Initializes the Memory instance and establishes the database connection.

        Args:
            db_path (str): Filepath for the SQLite database. Defaults to
                'dracula_memory.db'.
            max_messages (int): The maximum number of recent messages to
                keep in history. Defaults to 10.
        """
        self.db_path = Path(db_path) if db_path else DEFAULT_DB_PATH
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self.max_messages = max_messages
        self._init_db()

    def _init_db(self):
        """Creates the necessary database schema if it does not already exist.

        Sets up the 'history' table with auto-incrementing IDs and timestamps
        to ensure chronological tracking of dialogue parts.

        Raises:
            ChatException: If the database connection fails or the schema
                cannot be initialized.
        """
        try:
            with sqlite3.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute(
                    """
                            CREATE TABLE IF NOT EXISTS history(
                                id INTEGER PRIMARY KEY AUTOINCREMENT,
                                role TEXT NOT NULL,
                                content TEXT NOT NULL,
                                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)
                                """
                )
                conn.commit()

            logger.debug("SQLite memory system was successfully initialized.")

        except Exception as e:
            raise ChatException(f"Database could not be initialized: {str(e)}")

    def add_message(self, role: str, content: str):
        """Records a new interaction in the conversation history.

        This method persists a message to the database and automatically
        triggers a history trim to maintain the defined message limit.

        Args:
            role (str): The entity sending the message. Typically 'user'
                or 'model'.
            content (str): The literal text content of the message.
        """
        try:
            with sqlite3.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute(
                    "INSERT INTO history (role, content) VALUES (?, ?)", (role, content)
                )
                conn.commit()

                self._trim_history()

        except Exception as e:
            logger.error(f"An error occurred while saving the message: {e}")

    def _trim_history(self):
        """Enforces the message retention policy by removing oldest records.

        Calculates the current record count and executes a deletion of
        entries exceeding the 'max_messages' threshold, ensuring the
        active context remains performant.
        """
        try:
            with sqlite3.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute(
                    f"""
                        DELETE FROM history
                        WHERE id NOT IN (
                            SELECT id FROM history
                            ORDER BY id DESC LIMIT {self.max_messages}
                        )
                    """
                )
                conn.commit()

        except Exception as e:
            logger.error(f"A problem occured while clearing the memory: {str(e)}")

    def get_history(self) -> list:
        """Retrieves the full chronological conversation history.

        Fetches all stored records from the database and formats them
        into a structure compatible with the Google Gemini API.

        Returns:
            list: A list of dictionaries, where each dictionary contains
                'role' and 'content' keys.
        """
        try:
            with sqlite3.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute("SELECT role, content FROM history ORDER BY id ASC")

                rows = cursor.fetchall()
                return [{"role": r, "content": c} for r, c in rows]

        except Exception as e:
            logger.error(f"An error occurred while retrieving the history: {str(e)}")

    def clear(self):
        """Permanently deletes all records from the conversation history.

        Resets the dialogue state. This action is irreversible within
        the database.

        Raises:
            ChatException: If the truncation of the history table fails.
        """
        try:
            with sqlite3.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute("DELETE FROM history")
                conn.commit()

            logger.info(f"The conversation history has been cleared.")

        except Exception as e:
            raise ChatException(f"The memory could not be erased: {str(e)}")

    def save(self, filepath: str):
        """Exports the current SQLite history to a JSON-formatted file.

        Useful for creating portable backups or sharing conversation
        logs in a human-readable format.

        Args:
            filepath (str): The destination path for the generated JSON file.
        """
        history = self.get_history()
        Path(filepath).write_text(json.dumps(history, indent=4), encoding="utf-8")

    def load(self, filepath: str):
        """Migrates history from a JSON file into the SQLite database.

        Parses an existing JSON history file and populates the database
        records. This method clears existing database entries before
        performing the migration.

        Args:
            filepath (str): The path to the source JSON file.

        Raises:
            ChatException: If the file is inaccessible or the data
                format is invalid.
        """
        try:
            data = json.loads(Path(filepath).read_text(encoding="utf-8"))
            self.clear()
            for msg in data:
                self.add_message(msg["role"], msg["content"])

        except Exception as e:
            raise ChatException(f"Data could not be transferred: {str(e)}")


class AsyncMemory:
    """Handles asynchronous conversational state using aiosqlite.

    This class provides non-blocking database operations for chat history
    management, specifically designed for use within AsyncDracula. It
    ensures that the event loop remains unblocked during I/O operations.

    Attributes:
        db_path (Path): The file system path to the SQLite database.
        max_messages (int): The maximum number of messages to retain in
            the rolling history.
    """

    def __init__(self, db_path: str | Path = None, max_messages: int = 10):
        """Initializes AsyncMemory with database path and message limits.

        Args:
            db_path (str): Filepath for the SQLite database. Defaults to
                'dracula_memory.db'.
            max_messages (int): Maximum number of messages to keep in history.
                Defaults to 10.
        """
        self.db_path = Path(db_path) if db_path else DEFAULT_DB_PATH
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self.max_messages = max_messages

    async def initialize(self):
        """Asynchronously creates the database schema if it does not exist.

        This method should be called before performing any chat operations
        to ensure the 'history' table is ready.

        Raises:
            ChatException: If the database connection or table creation fails.
        """

        try:
            async with aiosqlite.connect(self.db_path) as db:
                await db.execute(
                    """
                            CREATE TABLE IF NOT EXISTS history (
                                id INTEGER PRIMARY KEY AUTOINCREMENT,
                                role TEXT NOT NULL,
                                content TEXT NOT NULL,
                                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
                            )
                        """
                )
                await db.commit()

            logger.debug("Async SQLite memory system initialized.")

        except Exception as e:
            raise ChatException(f"Failed to initialize async database: {str(e)}")

    async def add_message(self, role: str, content: str):
        """Asynchronously records a new message and trims the history.

        Args:
            role (str): The entity sending the message ('user' or 'model').
            content (str): The literal text content of the message.
        """

        try:
            async with aiosqlite.connect(self.db_path) as db:
                await db.execute(
                    "INSERT INTO history (role, content) VALUES (?, ?)", (role, content)
                )
                await db.commit()
                await self._trim_history(db)

        except Exception as e:
            logger.error(f"Error adding message to async memory: {str(e)}")

    async def _trim_history(self, db: aiosqlite.Connection):
        """Asynchronously removes oldest records exceeding the max limit.

        Args:
            db (aiosqlite.Connection): The active database connection.
        """

        try:
            await db.execute(
                f"""
                DELETE FROM history
                WHERE id NOT IN (
                    SELECT id FROM history
                    ORDER BY id DESC LIMIT {self.max_messages}
                    )
                """
            )
            await db.commit()

        except Exception as e:
            logger.warning(f"Failed to trim async history: {str(e)}")

    async def get_history(self) -> list:
        """Retrieves the chronological chat history asynchronously.

        Returns:
            list: A list of dictionaries with 'role' and 'content' keys.
        """
        try:
            async with aiosqlite.connect(self.db_path) as db:
                async with db.execute(
                    "SELECT role, content FROM history ORDER BY id ASC"
                ) as cursor:
                    rows = await cursor.fetchall()
                    return [{"role": r, "content": c} for r, c in rows]
        except Exception as e:
            logger.error(f"Error fetching async history: {str(e)}")
            return []

    async def clear(self):
        """Asynchronously deletes all conversational records.

        Raises:
            ChatException: If the database operation fails.
        """
        try:
            async with aiosqlite.connect(self.db_path) as db:
                await db.execute("DELETE FROM history")
                await db.commit()
            logger.info("Async conversation history cleared.")
        except Exception as e:
            raise ChatException(f"Failed to clear async memory: {str(e)}")
