"""
SQLite-backed conversation storage for TeLLMgramBot.

Provides async helpers for persisting and loading messages, managing per-user private mode,
and retrieving conversation history. The schema includes a messages table (indexed by chat_id
and user_id) and a private_mode table for tracking user preferences.
"""
import os
from typing import Optional

import aiosqlite
from .utils import execution_dir

# Allows tests to override the DB path without touching env vars
_DB_PATH: Optional[str] = None

_DDL = """
PRAGMA journal_mode=WAL;

CREATE TABLE IF NOT EXISTS messages (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    chat_id    INTEGER NOT NULL,
    user_id    INTEGER NOT NULL,
    username   TEXT,
    role       TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
    content    TEXT NOT NULL,
    is_private INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_messages_chat_id ON messages(chat_id, created_at);
CREATE INDEX IF NOT EXISTS idx_messages_user_id ON messages(user_id, created_at);

CREATE TABLE IF NOT EXISTS private_mode (
    user_id    INTEGER PRIMARY KEY,
    enabled    INTEGER NOT NULL DEFAULT 0,
    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
"""

def get_db_path() -> str:
    """
    Return the SQLite database file path.

    Reads TELLMGRAMBOT_DATA_PATH env var (directory path) and appends conversations.db.
    Falls back to data/conversations.db relative to the execution directory if unset.
    Module-level _DB_PATH overrides everything — used by tests.
    """
    if _DB_PATH:
        return _DB_PATH
    data_dir = os.environ.get('TELLMGRAMBOT_DATA_PATH', os.path.join(execution_dir(), 'data'))
    return os.path.join(data_dir, 'conversations.db')

async def init_db() -> None:
    """
    Create the messages and private_mode tables if they do not exist.
    Enables WAL journal mode for safe concurrent reads and writes.
    Safe to call repeatedly — all statements use IF NOT EXISTS.
    """
    async with aiosqlite.connect(get_db_path()) as db:
        await db.executescript(_DDL)
        await db.commit()

async def insert_message(
    chat_id: int,
    user_id: int,
    username: Optional[str],
    role: str,
    content: str,
    is_private: bool = False,
) -> None:
    """
    Persist a single message row.

    Args:
        chat_id: Telegram chat ID (negative for groups, positive/==user_id for private).
        user_id: Telegram user ID of the sender.
        username: Telegram username (display only, may be None).
        role: 'user', 'assistant', or 'system'.
        content: Message text.
        is_private: If True, this message is excluded from group context loading.
    """
    async with aiosqlite.connect(get_db_path()) as db:
        await db.execute(
            "INSERT INTO messages (chat_id, user_id, username, role, content, is_private) "
            "VALUES (?, ?, ?, ?, ?, ?)",
            (chat_id, user_id, username, role, content, int(is_private)),
        )
        await db.commit()

async def load_messages_for_chat(
    chat_id: int,
    exclude_private: bool = False,
) -> list[dict]:
    """
    Load all messages for a given chat in chronological order.

    Args:
        chat_id: Telegram chat ID to query.
        exclude_private: If True, rows with is_private=1 are excluded.
            Used when loading group conversation context.

    Returns:
        List of {"role": ..., "content": ...} dicts.
    """
    query = "SELECT role, content FROM messages WHERE chat_id = ?"
    params: tuple = (chat_id,)
    if exclude_private:
        query += " AND is_private = 0"
    query += " ORDER BY created_at ASC, id ASC"
    async with aiosqlite.connect(get_db_path()) as db:
        async with db.execute(query, params) as cursor:
            rows = await cursor.fetchall()
    return [{"role": row[0], "content": row[1]} for row in rows]

async def load_messages_for_user(
    user_id: int,
    exclude_private: bool = False,
) -> list[dict]:
    """
    Load all messages from a user's private chat history.

    In Telegram, a user's private chat_id equals their user_id. This function loads messages
    from that private chat, optionally filtering out private-mode messages.

    Args:
        user_id: Telegram user ID; also serves as the private chat_id.
        exclude_private: If True, rows with is_private=1 are excluded.

    Returns:
        List of {"role": ..., "content": ...} dicts in chronological order.
    """
    query = "SELECT role, content FROM messages WHERE chat_id = ?"
    params: tuple = (user_id,)
    if exclude_private:
        query += " AND is_private = 0"
    query += " ORDER BY created_at ASC, id ASC"
    async with aiosqlite.connect(get_db_path()) as db:
        async with db.execute(query, params) as cursor:
            rows = await cursor.fetchall()
    return [{"role": row[0], "content": row[1]} for row in rows]

async def load_full_user_context(
    user_id: int,
    current_chat_id: int,
    exclude_private: bool = False,
) -> list[dict]:
    """
    Load bidirectional conversation context for a user across all chats.

    Implements bidirectional cross-pollination: returns all messages from the current
    chat (all participants) plus only the requesting user's own messages from every
    other chat, merged in chronological order. This eliminates amnesia when a user
    switches between private and group contexts.

    Query logic: loads ALL rows from current_chat_id (all participants), plus only the requesting
    user's own rows from every other chat, merged chronologically. The exclude_private filter
    is applied globally when the current chat is a group.

    Args:
        user_id: Telegram user ID of the requesting user.
        current_chat_id: The chat_id of the current conversation (negative for groups, positive for private).
        exclude_private: If True, rows with is_private=1 are excluded from all chats.
            Applied when the current chat is a group so private-mode messages
            don't surface in shared contexts.

    Returns:
        List of {"role": ..., "content": ...} dicts in ascending chronological order (oldest first).
    """
    query = (
        "SELECT role, content FROM messages "
        "WHERE (chat_id = ? OR (user_id = ? AND chat_id != ?))"
    )
    params: tuple = (current_chat_id, user_id, current_chat_id)
    if exclude_private:
        query += " AND is_private = 0"
    query += " ORDER BY created_at ASC, id ASC"
    async with aiosqlite.connect(get_db_path()) as db:
        async with db.execute(query, params) as cursor:
            rows = await cursor.fetchall()
    return [{"role": row[0], "content": row[1]} for row in rows]

async def delete_messages_for_user(user_id: int) -> None:
    """
    Delete all message rows for a given user_id across all chats.
    Used by /forget in group chats — only the issuing user's rows are removed.
    """
    async with aiosqlite.connect(get_db_path()) as db:
        await db.execute("DELETE FROM messages WHERE user_id = ?", (user_id,))
        await db.commit()

async def delete_messages_for_chat(chat_id: int) -> None:
    """
    Delete all message rows for a given chat_id, including all participants and bot replies.
    Used by /forget in private chats to fully clear the conversation.
    """
    async with aiosqlite.connect(get_db_path()) as db:
        await db.execute("DELETE FROM messages WHERE chat_id = ?", (chat_id,))
        await db.commit()

async def get_private_mode(user_id: int) -> bool:
    """
    Return whether private mode is currently enabled for this user.
    Defaults to False if no row exists.
    """
    async with aiosqlite.connect(get_db_path()) as db:
        async with db.execute(
            "SELECT enabled FROM private_mode WHERE user_id = ?", (user_id,)
        ) as cursor:
            row = await cursor.fetchone()
    return bool(row[0]) if row else False

async def set_private_mode(user_id: int, enabled: bool) -> None:
    """
    Enable or disable private mode for a user.

    Upserts the private_mode row for this user_id. If the user already has a private_mode
    entry, it is updated; otherwise, a new row is created.

    Args:
        user_id: Telegram user ID.
        enabled: True to enable private mode, False to disable.
    """
    async with aiosqlite.connect(get_db_path()) as db:
        await db.execute(
            "INSERT INTO private_mode (user_id, enabled) VALUES (?, ?) "
            "ON CONFLICT(user_id) DO UPDATE SET enabled = excluded.enabled, "
            "updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')",
            (user_id, int(enabled)),
        )
        await db.commit()
