"""Tests for v9→v10 migration: FTS5 full-text search."""

from __future__ import annotations

import hashlib
import sqlite3

import pytest

from claude_engram.migration import get_schema_version, migrate_schema


def _create_v9_db(tmp_path) -> sqlite3.Connection:
    """Create a v9 database with all columns up to v9."""
    db_path = tmp_path / "v9_test.db"
    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row
    conn.executescript("""
        CREATE TABLE memories (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            content TEXT NOT NULL,
            category TEXT DEFAULT 'general',
            metadata TEXT DEFAULT '{}',
            created_at TEXT NOT NULL,
            updated_at TEXT NOT NULL,
            proj_tag TEXT DEFAULT '',
            ttl_tier TEXT DEFAULT 'permanent',
            expires_at TEXT DEFAULT NULL,
            content_hash TEXT DEFAULT NULL,
            summary TEXT DEFAULT NULL,
            token_count INTEGER DEFAULT NULL,
            superseded_by INTEGER DEFAULT NULL,
            is_active INTEGER DEFAULT 1,
            valid_from TEXT DEFAULT NULL,
            valid_to TEXT DEFAULT NULL,
            access_count INTEGER DEFAULT 0
        );

        CREATE TABLE IF NOT EXISTS counters (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            key TEXT NOT NULL,
            value INTEGER NOT NULL DEFAULT 0,
            proj_tag TEXT NOT NULL DEFAULT '',
            updated_at TEXT NOT NULL,
            UNIQUE(key, proj_tag)
        );

        CREATE TABLE IF NOT EXISTS memory_relations (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            source_id INTEGER NOT NULL,
            target_id INTEGER NOT NULL,
            relation_type TEXT NOT NULL,
            metadata TEXT DEFAULT '{}',
            created_at TEXT NOT NULL,
            valid_from TEXT DEFAULT NULL,
            valid_to TEXT DEFAULT NULL,
            invalidated_at TEXT DEFAULT NULL,
            UNIQUE(source_id, target_id, relation_type)
        );

        CREATE TABLE IF NOT EXISTS consolidation_meta (
            key TEXT PRIMARY KEY,
            value TEXT NOT NULL
        );

        CREATE INDEX IF NOT EXISTS idx_memories_is_active ON memories(is_active);
        CREATE INDEX IF NOT EXISTS idx_memories_temporal
            ON memories(valid_from, valid_to);
        CREATE INDEX IF NOT EXISTS idx_memories_access_count
            ON memories(access_count);
        CREATE INDEX IF NOT EXISTS idx_relations_source
            ON memory_relations(source_id, relation_type);
        CREATE INDEX IF NOT EXISTS idx_relations_target
            ON memory_relations(target_id, relation_type);

        PRAGMA user_version = 9;
    """)

    # Insert test memories with various content for FTS5 testing
    test_memories = [
        ("Phase 7.1 decision about auth strategy", "decision"),
        ("Debugging session for login flow", "lesson"),
        ("Authentication implementation complete", "observation"),
        ("Memory about testing procedures", "lesson"),
    ]

    for content, category in test_memories:
        h = hashlib.sha256(content.encode()).hexdigest()
        conn.execute(
            "INSERT INTO memories (content, category, created_at, updated_at, "
            "content_hash, proj_tag, valid_from) VALUES (?, ?, ?, ?, ?, ?, ?)",
            (
                content,
                category,
                "2025-01-01T00:00:00+00:00",
                "2025-01-01T00:00:00+00:00",
                h,
                "",
                "2025-01-01T00:00:00+00:00",
            ),
        )
    conn.commit()
    return conn


@pytest.fixture()
def v9_db(tmp_path) -> sqlite3.Connection:
    """Fixture for a v9 database."""
    return _create_v9_db(tmp_path)


class TestMigrateV9ToV10:
    """Tests for v9→v10 migration: FTS5 full-text search."""

    def test_migration_creates_fts_table(self, v9_db: sqlite3.Connection) -> None:
        """Migration should create memory_fts virtual table."""
        migrate_schema(v9_db)
        tables = v9_db.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='memory_fts'"
        ).fetchall()
        assert len(tables) == 1

    def test_migration_creates_triggers(self, v9_db: sqlite3.Connection) -> None:
        """Migration should create INSERT, UPDATE, DELETE triggers."""
        migrate_schema(v9_db)
        triggers = v9_db.execute("SELECT name FROM sqlite_master WHERE type='trigger'").fetchall()
        trigger_names = {t[0] if isinstance(t, tuple) else t["name"] for t in triggers}

        assert "memories_ai_fts" in trigger_names
        assert "memories_ad_fts" in trigger_names
        assert "memories_au_fts" in trigger_names

    def test_migration_sets_version_10(self, v9_db: sqlite3.Connection) -> None:
        """Migration should set PRAGMA user_version to 10."""
        migrate_schema(v9_db)
        assert get_schema_version(v9_db) == 10

    def test_migration_backfills_fts_index(self, v9_db: sqlite3.Connection) -> None:
        """Existing memories should be indexed in FTS5 after migration."""
        # Verify memories exist before migration
        count_before = v9_db.execute("SELECT COUNT(*) FROM memories").fetchone()[0]
        assert count_before > 0

        migrate_schema(v9_db)

        # Search for a word we know exists
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'Phase'"
        ).fetchall()
        assert len(results) > 0

    def test_idempotent_v10_migration(self, v9_db: sqlite3.Connection) -> None:
        """Running migration twice should be safe and maintain version 10."""
        migrate_schema(v9_db)
        assert get_schema_version(v9_db) == 10

        # Second run should not error
        migrate_schema(v9_db)
        assert get_schema_version(v9_db) == 10

    def test_full_v1_to_v10_migration(self, tmp_path) -> None:
        """Fresh v1 database should migrate all the way to v10."""
        # Create minimal v1 database
        db_path = tmp_path / "v1_test.db"
        conn = sqlite3.connect(str(db_path))
        conn.executescript("""
            CREATE TABLE memories (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                content TEXT NOT NULL,
                category TEXT DEFAULT 'general',
                metadata TEXT DEFAULT '{}',
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );
            CREATE INDEX idx_memories_category ON memories(category);
            CREATE INDEX idx_memories_created ON memories(created_at);
        """)
        conn.commit()

        assert get_schema_version(conn) == 0
        migrate_schema(conn)
        assert get_schema_version(conn) == 10

        # Verify FTS5 table exists
        tables = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='memory_fts'"
        ).fetchall()
        assert len(tables) == 1

    def test_trigger_sync_insert(self, v9_db: sqlite3.Connection) -> None:
        """INSERT trigger should add new memories to FTS5 index."""
        migrate_schema(v9_db)

        # Insert a new memory after migration
        h = hashlib.sha256(b"New test memory").hexdigest()
        v9_db.execute(
            "INSERT INTO memories (content, category, created_at, updated_at, "
            "content_hash, proj_tag) VALUES (?, ?, ?, ?, ?, ?)",
            ("New test memory", "lesson", "2025-02-01", "2025-02-01", h, ""),
        )
        v9_db.commit()

        # Verify FTS5 can find it
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'New'"
        ).fetchall()
        assert len(results) == 1

    def test_trigger_sync_delete(self, v9_db: sqlite3.Connection) -> None:
        """DELETE trigger should remove memories from FTS5 index."""
        migrate_schema(v9_db)

        # Insert and then delete a memory
        h = hashlib.sha256(b"Delete me").hexdigest()
        v9_db.execute(
            "INSERT INTO memories (content, category, created_at, updated_at, "
            "content_hash, proj_tag) VALUES (?, ?, ?, ?, ?, ?)",
            ("Delete me", "lesson", "2025-02-01", "2025-02-01", h, ""),
        )
        v9_db.commit()

        # Verify it's in FTS5
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'Delete'"
        ).fetchall()
        assert len(results) == 1
        row_id = results[0][0]

        # Delete the memory
        v9_db.execute("DELETE FROM memories WHERE id = ?", (row_id,))
        v9_db.commit()

        # Verify FTS5 no longer finds it
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'Delete'"
        ).fetchall()
        assert len(results) == 0

    def test_trigger_sync_update(self, v9_db: sqlite3.Connection) -> None:
        """UPDATE trigger should update FTS5 index when content changes."""
        migrate_schema(v9_db)

        # Insert a memory
        h = hashlib.sha256(b"Original content").hexdigest()
        v9_db.execute(
            "INSERT INTO memories (content, category, created_at, updated_at, "
            "content_hash, proj_tag) VALUES (?, ?, ?, ?, ?, ?)",
            ("Original content", "lesson", "2025-02-01", "2025-02-01", h, ""),
        )
        v9_db.commit()

        # Get the ID
        row = v9_db.execute("SELECT id FROM memories WHERE content = 'Original content'").fetchone()
        memory_id = row[0]

        # Update the content
        new_hash = hashlib.sha256(b"Updated content").hexdigest()
        v9_db.execute(
            "UPDATE memories SET content = ?, content_hash = ? WHERE id = ?",
            ("Updated content", new_hash, memory_id),
        )
        v9_db.commit()

        # Verify FTS5 finds new content
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'Updated'"
        ).fetchall()
        assert len(results) == 1

        # Verify FTS5 no longer finds old content
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'Original'"
        ).fetchall()
        assert len(results) == 0

    def test_update_trigger_when_clause_access_count(self, v9_db: sqlite3.Connection) -> None:
        """UPDATE trigger should NOT fire when only access_count changes."""
        migrate_schema(v9_db)

        # Insert a memory
        h = hashlib.sha256(b"Test content for access").hexdigest()
        v9_db.execute(
            "INSERT INTO memories (content, category, created_at, updated_at, "
            "content_hash, proj_tag, access_count) VALUES (?, ?, ?, ?, ?, ?, ?)",
            ("Test content for access", "lesson", "2025-02-01", "2025-02-01", h, "", 0),
        )
        v9_db.commit()

        # Get the ID
        row = v9_db.execute(
            "SELECT id FROM memories WHERE content = 'Test content for access'"
        ).fetchone()
        memory_id = row[0]

        # Update only access_count (should NOT trigger FTS5 update due to WHEN clause)
        v9_db.execute("UPDATE memories SET access_count = 5 WHERE id = ?", (memory_id,))
        v9_db.commit()

        # Verify FTS5 still finds the content (trigger didn't break anything)
        results = v9_db.execute(
            "SELECT rowid FROM memory_fts WHERE memory_fts MATCH 'access'"
        ).fetchall()
        assert len(results) == 1

    def test_migration_with_empty_db(self, tmp_path) -> None:
        """Migration on database with no memories should succeed."""
        db_path = tmp_path / "empty_v9.db"
        conn = sqlite3.connect(str(db_path))
        conn.executescript("""
            CREATE TABLE memories (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                content TEXT NOT NULL,
                category TEXT DEFAULT 'general',
                metadata TEXT DEFAULT '{}',
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                proj_tag TEXT DEFAULT '',
                ttl_tier TEXT DEFAULT 'permanent',
                expires_at TEXT DEFAULT NULL,
                content_hash TEXT DEFAULT NULL,
                summary TEXT DEFAULT NULL,
                token_count INTEGER DEFAULT NULL,
                superseded_by INTEGER DEFAULT NULL,
                is_active INTEGER DEFAULT 1,
                valid_from TEXT DEFAULT NULL,
                valid_to TEXT DEFAULT NULL,
                access_count INTEGER DEFAULT 0
            );
            PRAGMA user_version = 9;
        """)
        conn.commit()

        # Should not error on empty database
        migrate_schema(conn)
        assert get_schema_version(conn) == 10

        # FTS5 table should exist but be empty
        count = conn.execute("SELECT COUNT(*) FROM memory_fts").fetchone()[0]
        assert count == 0
