"""
SQLite database for cross-project messaging.

WHY: Provides persistent, efficient storage for messaging sessions and messages,
replacing the file-based approach with proper database management.

DESIGN:
- SQLite with WAL mode for concurrent access
- Two tables: sessions (peer discovery) and messages (communication)
- Supports both local project databases and global registry
- Efficient querying with indexes on common fields
"""

import json
import os
import sqlite3
from contextlib import contextmanager
from datetime import UTC, datetime
from pathlib import Path

from ...core.logging_utils import get_logger

logger = get_logger(__name__)


class MessagingDatabase:
    """SQLite database for cross-project messaging."""

    def __init__(self, db_path: Path):
        """
        Initialize database, create tables if needed.

        Args:
            db_path: Path to SQLite database file
        """
        self.db_path = db_path
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._initialize_database()

    @contextmanager
    def get_connection(self):
        """Get a database connection with proper handling."""
        conn = sqlite3.connect(str(self.db_path))
        conn.row_factory = sqlite3.Row
        try:
            # Enable WAL mode for concurrent access
            conn.execute("PRAGMA journal_mode=WAL")
            yield conn
            conn.commit()
        except Exception as e:
            conn.rollback()
            raise e
        finally:
            conn.close()

    def _initialize_database(self) -> None:
        """Create tables and indexes if they don't exist."""
        with self.get_connection() as conn:
            # Create sessions table
            conn.execute("""
                CREATE TABLE IF NOT EXISTS sessions (
                    session_id TEXT PRIMARY KEY,
                    project_path TEXT NOT NULL,
                    project_name TEXT NOT NULL,
                    started_at TEXT NOT NULL,
                    last_active TEXT NOT NULL,
                    status TEXT NOT NULL DEFAULT 'active',
                    pid INTEGER
                )
            """)

            # Create messages table
            conn.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id TEXT PRIMARY KEY,
                    from_project TEXT NOT NULL,
                    from_agent TEXT NOT NULL DEFAULT 'pm',
                    to_project TEXT NOT NULL,
                    to_agent TEXT NOT NULL DEFAULT 'pm',
                    message_type TEXT NOT NULL DEFAULT 'notification',
                    priority TEXT NOT NULL DEFAULT 'normal',
                    subject TEXT NOT NULL,
                    body TEXT NOT NULL,
                    status TEXT NOT NULL DEFAULT 'unread',
                    created_at TEXT NOT NULL,
                    read_at TEXT,
                    replied_to TEXT,
                    task_injected INTEGER NOT NULL DEFAULT 0,
                    metadata TEXT,
                    attachments TEXT
                )
            """)

            # Create indexes for efficient querying
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_messages_status ON messages(status)"
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_messages_to_project ON messages(to_project)"
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_messages_priority ON messages(priority)"
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at)"
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status)"
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_sessions_last_active ON sessions(last_active)"
            )

    # Message operations

    def insert_message(self, message: dict) -> str:
        """
        Insert a new message into the database.

        Args:
            message: Message dictionary with required fields

        Returns:
            Message ID
        """
        with self.get_connection() as conn:
            # Serialize JSON fields
            metadata_json = json.dumps(message.get("metadata", {}))
            attachments_json = json.dumps(message.get("attachments", []))

            conn.execute(
                """
                INSERT INTO messages (
                    id, from_project, from_agent, to_project, to_agent,
                    message_type, priority, subject, body, status,
                    created_at, replied_to, metadata, attachments
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    message["id"],
                    message["from_project"],
                    message.get("from_agent", "pm"),
                    message["to_project"],
                    message.get("to_agent", "pm"),
                    message.get("type", "notification"),
                    message.get("priority", "normal"),
                    message["subject"],
                    message["body"],
                    message.get("status", "unread"),
                    message.get("created_at", datetime.now(UTC).isoformat()),
                    message.get("reply_to"),
                    metadata_json,
                    attachments_json,
                ),
            )

        logger.debug(f"Inserted message {message['id']}")
        return message["id"]

    def get_message(self, message_id: str) -> dict | None:
        """
        Get a message by ID.

        Args:
            message_id: Message ID

        Returns:
            Message dict or None if not found
        """
        with self.get_connection() as conn:
            cursor = conn.execute("SELECT * FROM messages WHERE id = ?", (message_id,))
            row = cursor.fetchone()

            if row:
                return self._row_to_message_dict(row)
            return None

    def list_messages(self, status: str | None = None, limit: int = 50) -> list[dict]:
        """
        List messages with optional filtering.

        Args:
            status: Filter by status (unread, read, archived)
            limit: Maximum number of messages to return

        Returns:
            List of message dictionaries
        """
        with self.get_connection() as conn:
            query = "SELECT * FROM messages"
            params = []

            if status:
                query += " WHERE status = ?"
                params.append(status)

            query += " ORDER BY created_at DESC LIMIT ?"
            params.append(limit)

            cursor = conn.execute(query, params)
            return [self._row_to_message_dict(row) for row in cursor.fetchall()]

    def update_message_status(self, message_id: str, status: str) -> bool:
        """
        Update message status.

        Args:
            message_id: Message ID
            status: New status (read, archived, etc.)

        Returns:
            True if updated, False if not found
        """
        with self.get_connection() as conn:
            # Update read_at if marking as read
            if status == "read":
                cursor = conn.execute(
                    """
                    UPDATE messages
                    SET status = ?, read_at = ?
                    WHERE id = ?
                    """,
                    (status, datetime.now(UTC).isoformat(), message_id),
                )
            else:
                cursor = conn.execute(
                    "UPDATE messages SET status = ? WHERE id = ?",
                    (status, message_id),
                )

            return cursor.rowcount > 0

    def mark_task_injected(self, message_id: str) -> bool:
        """
        Mark a message as having its task injected.

        Args:
            message_id: Message ID

        Returns:
            True if updated, False if not found
        """
        with self.get_connection() as conn:
            cursor = conn.execute(
                "UPDATE messages SET task_injected = 1 WHERE id = ?",
                (message_id,),
            )
            return cursor.rowcount > 0

    def get_unread_count(self, to_agent: str | None = None) -> int:
        """
        Get count of unread messages.

        Args:
            to_agent: Optional filter by target agent

        Returns:
            Number of unread messages
        """
        with self.get_connection() as conn:
            if to_agent:
                cursor = conn.execute(
                    "SELECT COUNT(*) FROM messages WHERE status = 'unread' AND to_agent = ?",
                    (to_agent,),
                )
            else:
                cursor = conn.execute(
                    "SELECT COUNT(*) FROM messages WHERE status = 'unread'"
                )

            return cursor.fetchone()[0]

    # Session operations

    def register_session(
        self, session_id: str, project_path: str, pid: int | None = None
    ) -> None:
        """
        Register a new session or update existing one.

        Args:
            session_id: Unique session identifier
            project_path: Absolute path to project
            pid: Process ID (optional)
        """
        project_name = Path(project_path).name
        now = datetime.now(UTC).isoformat()

        with self.get_connection() as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO sessions (
                    session_id, project_path, project_name,
                    started_at, last_active, status, pid
                ) VALUES (?, ?, ?, ?, ?, 'active', ?)
                """,
                (
                    session_id,
                    project_path,
                    project_name,
                    now,
                    now,
                    pid or os.getpid(),
                ),
            )

        logger.debug(f"Registered session {session_id} for {project_name}")

    def update_heartbeat(self, session_id: str) -> None:
        """
        Update session heartbeat timestamp.

        Args:
            session_id: Session ID to update
        """
        with self.get_connection() as conn:
            conn.execute(
                "UPDATE sessions SET last_active = ? WHERE session_id = ?",
                (datetime.now(UTC).isoformat(), session_id),
            )

    def deregister_session(self, session_id: str) -> None:
        """
        Mark session as inactive.

        Args:
            session_id: Session ID to deregister
        """
        with self.get_connection() as conn:
            conn.execute(
                "UPDATE sessions SET status = 'inactive' WHERE session_id = ?",
                (session_id,),
            )
        logger.debug(f"Deregistered session {session_id}")

    def list_active_sessions(self) -> list[dict]:
        """
        List all active sessions.

        Returns:
            List of session dictionaries
        """
        with self.get_connection() as conn:
            cursor = conn.execute(
                """
                SELECT * FROM sessions
                WHERE status = 'active'
                ORDER BY last_active DESC
                """
            )
            return [
                {
                    "session_id": row["session_id"],
                    "project_path": row["project_path"],
                    "project_name": row["project_name"],
                    "started_at": row["started_at"],
                    "last_active": row["last_active"],
                    "status": row["status"],
                    "pid": row["pid"],
                }
                for row in cursor.fetchall()
            ]

    def list_all_sessions(self) -> list[dict]:
        """
        List all sessions regardless of status.

        Returns:
            List of session dictionaries
        """
        with self.get_connection() as conn:
            cursor = conn.execute(
                """
                SELECT * FROM sessions
                ORDER BY last_active DESC
                """
            )
            return [
                {
                    "session_id": row["session_id"],
                    "project_path": row["project_path"],
                    "project_name": row["project_name"],
                    "started_at": row["started_at"],
                    "last_active": row["last_active"],
                    "status": row["status"],
                    "pid": row["pid"],
                }
                for row in cursor.fetchall()
            ]

    def cleanup_stale_sessions(self, timeout_minutes: int = 60) -> int:
        """
        Mark stale sessions as inactive.

        Args:
            timeout_minutes: Minutes of inactivity before marking stale

        Returns:
            Number of sessions marked as stale
        """
        from datetime import timedelta

        cutoff_time = (
            datetime.now(UTC) - timedelta(minutes=timeout_minutes)
        ).isoformat()

        with self.get_connection() as conn:
            cursor = conn.execute(
                """
                UPDATE sessions
                SET status = 'stale'
                WHERE status = 'active' AND last_active < ?
                """,
                (cutoff_time,),
            )
            count = cursor.rowcount

        if count > 0:
            logger.info(f"Marked {count} sessions as stale")

        return count

    # Helper methods

    def _row_to_message_dict(self, row: sqlite3.Row) -> dict:
        """Convert database row to message dictionary."""
        return {
            "id": row["id"],
            "from_project": row["from_project"],
            "from_agent": row["from_agent"],
            "to_project": row["to_project"],
            "to_agent": row["to_agent"],
            "type": row["message_type"],
            "priority": row["priority"],
            "subject": row["subject"],
            "body": row["body"],
            "status": row["status"],
            "created_at": row["created_at"],
            "read_at": row["read_at"],
            "reply_to": row["replied_to"],
            "task_injected": bool(row["task_injected"]),
            "metadata": json.loads(row["metadata"] or "{}"),
            "attachments": json.loads(row["attachments"] or "[]"),
        }

    def get_messages_for_agent(
        self, to_agent: str, status: str | None = None, limit: int = 50
    ) -> list[dict]:
        """
        Get messages for a specific agent.

        Args:
            to_agent: Target agent name
            status: Optional status filter
            limit: Maximum number of messages

        Returns:
            List of message dictionaries
        """
        with self.get_connection() as conn:
            if status:
                cursor = conn.execute(
                    """
                    SELECT * FROM messages
                    WHERE to_agent = ? AND status = ?
                    ORDER BY created_at DESC LIMIT ?
                    """,
                    (to_agent, status, limit),
                )
            else:
                cursor = conn.execute(
                    """
                    SELECT * FROM messages
                    WHERE to_agent = ?
                    ORDER BY created_at DESC LIMIT ?
                    """,
                    (to_agent, limit),
                )

            return [self._row_to_message_dict(row) for row in cursor.fetchall()]

    def get_high_priority_messages(
        self, priorities: list[str] | None = None, limit: int = 50
    ) -> list[dict]:
        """
        Get high priority messages.

        Args:
            priorities: List of priority levels to include
            limit: Maximum number of messages

        Returns:
            List of high priority messages
        """
        if not priorities:
            priorities = ["high", "urgent"]

        with self.get_connection() as conn:
            placeholders = ",".join("?" * len(priorities))
            cursor = conn.execute(
                f"""
                SELECT * FROM messages
                WHERE priority IN ({placeholders}) AND status = 'unread'
                ORDER BY
                    CASE priority
                        WHEN 'urgent' THEN 0
                        WHEN 'high' THEN 1
                        ELSE 2
                    END,
                    created_at DESC
                LIMIT ?
                """,  # nosec B608
                (*priorities, limit),
            )

            return [self._row_to_message_dict(row) for row in cursor.fetchall()]

    # New methods for project-filtered queries (for shared database)

    def get_messages_for_project(
        self, project_path: str, status: str | None = None, limit: int = 50
    ) -> list[dict]:
        """
        Get messages for a specific project.

        Args:
            project_path: Target project path
            status: Optional status filter
            limit: Maximum number of messages

        Returns:
            List of message dictionaries
        """
        with self.get_connection() as conn:
            if status:
                cursor = conn.execute(
                    """
                    SELECT * FROM messages
                    WHERE to_project = ? AND status = ?
                    ORDER BY created_at DESC LIMIT ?
                    """,
                    (project_path, status, limit),
                )
            else:
                cursor = conn.execute(
                    """
                    SELECT * FROM messages
                    WHERE to_project = ?
                    ORDER BY created_at DESC LIMIT ?
                    """,
                    (project_path, limit),
                )

            return [self._row_to_message_dict(row) for row in cursor.fetchall()]

    def get_messages_for_project_and_agent(
        self,
        project_path: str,
        to_agent: str,
        status: str | None = None,
        limit: int = 50,
    ) -> list[dict]:
        """
        Get messages for a specific project and agent.

        Args:
            project_path: Target project path
            to_agent: Target agent name
            status: Optional status filter
            limit: Maximum number of messages

        Returns:
            List of message dictionaries
        """
        with self.get_connection() as conn:
            if status:
                cursor = conn.execute(
                    """
                    SELECT * FROM messages
                    WHERE to_project = ? AND to_agent = ? AND status = ?
                    ORDER BY created_at DESC LIMIT ?
                    """,
                    (project_path, to_agent, status, limit),
                )
            else:
                cursor = conn.execute(
                    """
                    SELECT * FROM messages
                    WHERE to_project = ? AND to_agent = ?
                    ORDER BY created_at DESC LIMIT ?
                    """,
                    (project_path, to_agent, limit),
                )

            return [self._row_to_message_dict(row) for row in cursor.fetchall()]

    def get_unread_count_for_project(
        self, project_path: str, to_agent: str | None = None
    ) -> int:
        """
        Get count of unread messages for a specific project.

        Args:
            project_path: Target project path
            to_agent: Optional filter by target agent

        Returns:
            Number of unread messages
        """
        with self.get_connection() as conn:
            if to_agent:
                cursor = conn.execute(
                    """
                    SELECT COUNT(*) FROM messages
                    WHERE to_project = ? AND to_agent = ? AND status = 'unread'
                    """,
                    (project_path, to_agent),
                )
            else:
                cursor = conn.execute(
                    """
                    SELECT COUNT(*) FROM messages
                    WHERE to_project = ? AND status = 'unread'
                    """,
                    (project_path,),
                )

            return cursor.fetchone()[0]

    def get_high_priority_messages_for_project(
        self, project_path: str, priorities: list[str] | None = None, limit: int = 50
    ) -> list[dict]:
        """
        Get high priority messages for a specific project.

        Args:
            project_path: Target project path
            priorities: List of priority levels to include
            limit: Maximum number of messages

        Returns:
            List of high priority messages
        """
        if not priorities:
            priorities = ["high", "urgent"]

        with self.get_connection() as conn:
            placeholders = ",".join("?" * len(priorities))
            cursor = conn.execute(
                f"""
                SELECT * FROM messages
                WHERE to_project = ? AND priority IN ({placeholders}) AND status = 'unread'
                ORDER BY
                    CASE priority
                        WHEN 'urgent' THEN 0
                        WHEN 'high' THEN 1
                        ELSE 2
                    END,
                    created_at DESC
                LIMIT ?
                """,  # nosec B608
                (project_path, *priorities, limit),
            )

            return [self._row_to_message_dict(row) for row in cursor.fetchall()]
