"""SQLite persistence layer for Mnemosynth.

Handles CRUD operations for MemoryNode records, provenance tracking,
and relationship edges. All data stored in a single portable SQLite file.
"""

from __future__ import annotations

import json
import sqlite3
from contextlib import contextmanager
from datetime import datetime, timezone
from typing import Optional, Generator

from mnemosynth.core.types import (
    MemoryNode,
    MemoryType,
    MemoryStatus,
    MemorySource,
    ExtractionMethod,
    RelationshipEdge,
)


SCHEMA_SQL = """
CREATE TABLE IF NOT EXISTS memories (
    id TEXT PRIMARY KEY,
    content TEXT NOT NULL,
    memory_type TEXT NOT NULL,
    confidence REAL DEFAULT 0.85,
    created_at TEXT NOT NULL,
    last_accessed TEXT NOT NULL,
    access_count INTEGER DEFAULT 0,
    status TEXT DEFAULT 'active',
    sentiment_score REAL DEFAULT 0.0,
    valid_from TEXT NOT NULL,
    valid_until TEXT,
    supersedes TEXT,
    superseded_by TEXT,
    corroboration_count INTEGER DEFAULT 0,
    tags TEXT DEFAULT '[]',
    source_json TEXT DEFAULT '{}'
);

CREATE TABLE IF NOT EXISTS relationships (
    source_id TEXT NOT NULL,
    target_id TEXT NOT NULL,
    relation_type TEXT NOT NULL,
    confidence REAL DEFAULT 0.8,
    created_at TEXT NOT NULL,
    valid_from TEXT NOT NULL,
    valid_until TEXT,
    metadata_json TEXT DEFAULT '{}',
    PRIMARY KEY (source_id, target_id, relation_type)
);

CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type);
CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
CREATE INDEX IF NOT EXISTS idx_memories_confidence ON memories(confidence);
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_id);
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_id);
"""


class DatabaseManager:
    """SQLite database manager for Mnemosynth memories."""

    def __init__(self, db_path: str):
        self.db_path = db_path
        self._init_db()

    def _init_db(self) -> None:
        """Create tables if they don't exist."""
        with self._connect() as conn:
            conn.executescript(SCHEMA_SQL)

    @contextmanager
    def _connect(self) -> Generator[sqlite3.Connection, None, None]:
        """Context manager for database connections."""
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        conn.execute("PRAGMA journal_mode=WAL")
        conn.execute("PRAGMA foreign_keys=ON")
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    # ── Memory CRUD ──────────────────────────────────────────────

    def save_memory(self, memory: MemoryNode) -> None:
        """Insert or update a memory node."""
        with self._connect() as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO memories
                (id, content, memory_type, confidence, created_at, last_accessed,
                 access_count, status, sentiment_score, valid_from, valid_until,
                 supersedes, superseded_by, corroboration_count, tags, source_json)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    memory.id,
                    memory.content,
                    memory.memory_type.value,
                    memory.confidence,
                    memory.created_at.isoformat(),
                    memory.last_accessed.isoformat(),
                    memory.access_count,
                    memory.status.value,
                    memory.sentiment_score,
                    memory.valid_from.isoformat(),
                    memory.valid_until.isoformat() if memory.valid_until else None,
                    memory.supersedes,
                    memory.superseded_by,
                    memory.corroboration_count,
                    json.dumps(memory.tags),
                    json.dumps(memory.source.to_dict()),
                ),
            )

    def get_memory(self, memory_id: str) -> Optional[MemoryNode]:
        """Retrieve a memory by ID."""
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM memories WHERE id = ?", (memory_id,)
            ).fetchone()
            if row is None:
                return None
            return self._row_to_memory(row)

    def get_memories(
        self,
        memory_type: Optional[MemoryType] = None,
        status: Optional[MemoryStatus] = None,
        limit: int = 100,
        offset: int = 0,
    ) -> list[MemoryNode]:
        """Query memories with optional filtering."""
        query = "SELECT * FROM memories WHERE 1=1"
        params: list = []

        if memory_type is not None:
            query += " AND memory_type = ?"
            params.append(memory_type.value)
        if status is not None:
            query += " AND status = ?"
            params.append(status.value)

        query += " ORDER BY last_accessed DESC LIMIT ? OFFSET ?"
        params.extend([limit, offset])

        with self._connect() as conn:
            rows = conn.execute(query, params).fetchall()
            return [self._row_to_memory(row) for row in rows]

    def search_memories_text(self, query: str, limit: int = 10) -> list[MemoryNode]:
        """Simple text search using LIKE (fallback for non-vector search)."""
        with self._connect() as conn:
            rows = conn.execute(
                """
                SELECT * FROM memories
                WHERE content LIKE ? AND status = 'active'
                ORDER BY confidence DESC, last_accessed DESC
                LIMIT ?
                """,
                (f"%{query}%", limit),
            ).fetchall()
            return [self._row_to_memory(row) for row in rows]

    def delete_memory(self, memory_id: str) -> bool:
        """Hard delete a memory."""
        with self._connect() as conn:
            cursor = conn.execute("DELETE FROM memories WHERE id = ?", (memory_id,))
            return cursor.rowcount > 0

    def count_memories(
        self,
        memory_type: Optional[MemoryType] = None,
        status: Optional[MemoryStatus] = None,
    ) -> int:
        """Count memories with optional filtering."""
        query = "SELECT COUNT(*) FROM memories WHERE 1=1"
        params: list = []

        if memory_type is not None:
            query += " AND memory_type = ?"
            params.append(memory_type.value)
        if status is not None:
            query += " AND status = ?"
            params.append(status.value)

        with self._connect() as conn:
            row = conn.execute(query, params).fetchone()
            return row[0]

    def get_recent_memories(
        self,
        days: int = 7,
        memory_type: Optional[MemoryType] = None,
    ) -> list[MemoryNode]:
        """Get memories from the last N days."""
        cutoff = datetime.now(timezone.utc).isoformat()
        # Simple approach: get all and filter in Python
        # SQLite doesn't have great datetime support
        query = "SELECT * FROM memories WHERE status = 'active'"
        params: list = []

        if memory_type is not None:
            query += " AND memory_type = ?"
            params.append(memory_type.value)

        query += " ORDER BY created_at DESC LIMIT 500"

        with self._connect() as conn:
            rows = conn.execute(query, params).fetchall()

        memories = [self._row_to_memory(row) for row in rows]

        # Filter by date
        from datetime import timedelta
        cutoff_dt = datetime.now(timezone.utc) - timedelta(days=days)
        return [m for m in memories if m.created_at >= cutoff_dt]

    # ── Relationships ────────────────────────────────────────────

    def save_relationship(self, edge: RelationshipEdge) -> None:
        """Insert or update a relationship edge."""
        with self._connect() as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO relationships
                (source_id, target_id, relation_type, confidence,
                 created_at, valid_from, valid_until, metadata_json)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    edge.source_id,
                    edge.target_id,
                    edge.relation_type,
                    edge.confidence,
                    edge.created_at.isoformat(),
                    edge.valid_from.isoformat(),
                    edge.valid_until.isoformat() if edge.valid_until else None,
                    json.dumps(edge.metadata),
                ),
            )

    def get_relationships(
        self,
        node_id: str,
        direction: str = "both",
    ) -> list[RelationshipEdge]:
        """Get all relationships for a node."""
        edges = []
        with self._connect() as conn:
            if direction in ("outgoing", "both"):
                rows = conn.execute(
                    "SELECT * FROM relationships WHERE source_id = ?",
                    (node_id,),
                ).fetchall()
                edges.extend([self._row_to_edge(r) for r in rows])

            if direction in ("incoming", "both"):
                rows = conn.execute(
                    "SELECT * FROM relationships WHERE target_id = ?",
                    (node_id,),
                ).fetchall()
                edges.extend([self._row_to_edge(r) for r in rows])

        return edges

    # ── Private helpers ──────────────────────────────────────────

    def _row_to_memory(self, row: sqlite3.Row) -> MemoryNode:
        """Convert a database row to a MemoryNode."""
        source_data = json.loads(row["source_json"]) if row["source_json"] else {}
        tags = json.loads(row["tags"]) if row["tags"] else []

        return MemoryNode(
            id=row["id"],
            content=row["content"],
            memory_type=MemoryType(row["memory_type"]),
            confidence=row["confidence"],
            created_at=datetime.fromisoformat(row["created_at"]),
            last_accessed=datetime.fromisoformat(row["last_accessed"]),
            access_count=row["access_count"],
            source=MemorySource.from_dict(source_data),
            status=MemoryStatus(row["status"]),
            sentiment_score=row["sentiment_score"],
            valid_from=datetime.fromisoformat(row["valid_from"]),
            valid_until=(
                datetime.fromisoformat(row["valid_until"]) if row["valid_until"] else None
            ),
            supersedes=row["supersedes"],
            superseded_by=row["superseded_by"],
            corroboration_count=row["corroboration_count"],
            tags=tags,
        )

    def _row_to_edge(self, row: sqlite3.Row) -> RelationshipEdge:
        """Convert a database row to a RelationshipEdge."""
        return RelationshipEdge(
            source_id=row["source_id"],
            target_id=row["target_id"],
            relation_type=row["relation_type"],
            confidence=row["confidence"],
            created_at=datetime.fromisoformat(row["created_at"]),
            valid_from=datetime.fromisoformat(row["valid_from"]),
            valid_until=(
                datetime.fromisoformat(row["valid_until"]) if row["valid_until"] else None
            ),
            metadata=json.loads(row["metadata_json"]) if row["metadata_json"] else {},
        )
