"""Tests for db_path parameterization in threads.py.

Validates that _connect(), get_checkpointer(), and public thread functions
accept an explicit db_path, bypassing the global get_db_path() default.
"""

from __future__ import annotations

import json
from pathlib import Path

import pytest


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

async def _create_checkpoints_table(db_path: Path) -> None:
    """Create a minimal checkpoints table + insert a row in the given DB."""
    import aiosqlite

    async with aiosqlite.connect(str(db_path)) as conn:
        await conn.execute("""
            CREATE TABLE IF NOT EXISTS checkpoints (
                thread_id TEXT,
                checkpoint_ns TEXT DEFAULT '',
                checkpoint_id TEXT DEFAULT '1',
                parent_checkpoint_id TEXT,
                type TEXT,
                checkpoint BLOB,
                metadata TEXT
            )
        """)
        metadata = json.dumps({
            "agent_name": "test-agent",
            "updated_at": "2025-06-01T12:00:00",
        })
        await conn.execute(
            "INSERT INTO checkpoints (thread_id, metadata) VALUES (?, ?)",
            ("thread-001", metadata),
        )
        await conn.commit()


# ---------------------------------------------------------------------------
# Task 1: _connect(db_path=...) and get_checkpointer(db_path=...)
# ---------------------------------------------------------------------------

class TestConnectDbPath:
    """Verify _connect() accepts an explicit db_path."""

    @pytest.mark.asyncio
    async def test_connect_with_explicit_path(self, tmp_path: Path) -> None:
        """_connect(db_path=...) should open the specified file, not the global one."""
        from icarus.session.threads import _connect, _table_exists

        db_path = tmp_path / "custom.db"
        await _create_checkpoints_table(db_path)

        async with _connect(db_path=db_path) as conn:
            assert await _table_exists(conn, "checkpoints")

    @pytest.mark.asyncio
    async def test_connect_none_falls_back(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        """_connect(db_path=None) should fall back to get_db_path()."""
        from icarus.session.threads import _connect, _table_exists

        fallback = tmp_path / "fallback.db"
        monkeypatch.setattr("icarus.session.threads.get_db_path", lambda: fallback)
        await _create_checkpoints_table(fallback)

        async with _connect() as conn:
            assert await _table_exists(conn, "checkpoints")

    @pytest.mark.asyncio
    async def test_connect_creates_new_db(self, tmp_path: Path) -> None:
        """_connect(db_path=...) on a non-existent file should create it."""
        from icarus.session.threads import _connect

        db_path = tmp_path / "brand_new.db"
        assert not db_path.exists()

        async with _connect(db_path=db_path) as conn:
            await conn.execute("CREATE TABLE test (id INTEGER)")

        assert db_path.exists()


class TestGetCheckpointerDbPath:
    """Verify get_checkpointer() accepts an explicit db_path."""

    @pytest.mark.asyncio
    async def test_checkpointer_with_explicit_path(self, tmp_path: Path) -> None:
        """get_checkpointer(db_path=...) should target the specified file."""
        from icarus.session.threads import get_checkpointer

        db_path = tmp_path / "checkpointer.db"

        async with get_checkpointer(db_path=db_path) as cp:
            # The checkpointer should have been created successfully.
            # We just verify it's usable (setup creates the tables).
            await cp.setup()

        assert db_path.exists()

    @pytest.mark.asyncio
    async def test_checkpointer_none_falls_back(
        self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
    ) -> None:
        """get_checkpointer(db_path=None) should fall back to get_db_path()."""
        from icarus.session.threads import get_checkpointer

        fallback = tmp_path / "cp_fallback.db"
        monkeypatch.setattr("icarus.session.threads.get_db_path", lambda: fallback)

        async with get_checkpointer() as cp:
            await cp.setup()

        assert fallback.exists()


# ---------------------------------------------------------------------------
# Task 2: Public thread functions with db_path
# ---------------------------------------------------------------------------

class TestListThreadsDbPath:
    """Verify list_threads(db_path=...) queries the specified database."""

    @pytest.mark.asyncio
    async def test_list_threads_explicit_path(self, tmp_path: Path) -> None:
        """list_threads should find threads in the explicit db_path."""
        from icarus.session.threads import list_threads

        db_path = tmp_path / "list.db"
        await _create_checkpoints_table(db_path)

        threads = await list_threads(db_path=db_path)
        assert len(threads) == 1
        assert threads[0]["thread_id"] == "thread-001"

    @pytest.mark.asyncio
    async def test_list_threads_empty_db(self, tmp_path: Path) -> None:
        """list_threads on a fresh DB with no checkpoints table returns []."""
        from icarus.session.threads import list_threads

        db_path = tmp_path / "empty.db"
        threads = await list_threads(db_path=db_path)
        assert threads == []

    @pytest.mark.asyncio
    async def test_list_threads_isolation(self, tmp_path: Path) -> None:
        """Two separate db_path files should have independent thread data."""
        from icarus.session.threads import list_threads

        import aiosqlite

        db_a = tmp_path / "a.db"
        db_b = tmp_path / "b.db"

        await _create_checkpoints_table(db_a)

        # db_b has a different thread
        async with aiosqlite.connect(str(db_b)) as conn:
            await conn.execute("""
                CREATE TABLE checkpoints (
                    thread_id TEXT, checkpoint_ns TEXT DEFAULT '',
                    checkpoint_id TEXT DEFAULT '1', parent_checkpoint_id TEXT,
                    type TEXT, checkpoint BLOB, metadata TEXT
                )
            """)
            metadata = json.dumps({"agent_name": "other", "updated_at": "2025-07-01T00:00:00"})
            await conn.execute(
                "INSERT INTO checkpoints (thread_id, metadata) VALUES (?, ?)",
                ("thread-other", metadata),
            )
            await conn.commit()

        threads_a = await list_threads(db_path=db_a)
        threads_b = await list_threads(db_path=db_b)

        assert len(threads_a) == 1
        assert threads_a[0]["thread_id"] == "thread-001"
        assert len(threads_b) == 1
        assert threads_b[0]["thread_id"] == "thread-other"


class TestRenameThreadDbPath:
    """Verify rename_thread(db_path=...) writes to the specified database."""

    @pytest.mark.asyncio
    async def test_rename_thread_explicit_path(self, tmp_path: Path) -> None:
        """rename_thread should write the title to the explicit db_path."""
        from icarus.session.threads import _connect, has_custom_title, rename_thread

        db_path = tmp_path / "rename.db"

        await rename_thread("thread-x", "My Title", db_path=db_path)
        assert await has_custom_title("thread-x", db_path=db_path)

        # Verify the title is actually in the right DB
        async with _connect(db_path=db_path) as conn:
            async with conn.execute(
                "SELECT custom_title FROM thread_metadata WHERE thread_id = ?",
                ("thread-x",),
            ) as cur:
                row = await cur.fetchone()
                assert row is not None
                assert row[0] == "My Title"


class TestHasCustomTitleDbPath:
    """Verify has_custom_title(db_path=...) queries the specified database."""

    @pytest.mark.asyncio
    async def test_has_custom_title_explicit_path(self, tmp_path: Path) -> None:
        """has_custom_title should check the explicit db_path."""
        from icarus.session.threads import has_custom_title, rename_thread

        db_path = tmp_path / "title.db"

        assert not await has_custom_title("thread-y", db_path=db_path)
        await rename_thread("thread-y", "Custom", db_path=db_path)
        assert await has_custom_title("thread-y", db_path=db_path)


class TestDeleteThreadDbPath:
    """Verify delete_thread(db_path=...) deletes from the specified database."""

    @pytest.mark.asyncio
    async def test_delete_thread_explicit_path(self, tmp_path: Path) -> None:
        """delete_thread should remove the thread from the explicit db_path."""
        from icarus.session.threads import delete_thread, list_threads

        db_path = tmp_path / "delete.db"
        await _create_checkpoints_table(db_path)

        threads = await list_threads(db_path=db_path)
        assert len(threads) == 1

        await delete_thread("thread-001", db_path=db_path)

        threads = await list_threads(db_path=db_path)
        assert len(threads) == 0
