"""Tests for ConversationLog — real SQLite, temp directory."""

import shutil
import time

import pytest

from neo_cortex.conversation_log import ConversationLog
from neo_cortex.models import ConversationAppendRequest


@pytest.fixture
def log(tmp_path) -> ConversationLog:
    return ConversationLog(str(tmp_path / "test.db"))


class TestConversationLogAppend:
    def test_append_and_count(self, log):
        assert log.count() == 0
        req = ConversationAppendRequest(
            session_id="s1", role="user", content="Hello",
        )
        row_id = log.append(req)
        assert row_id == 1
        assert log.count() == 1

    def test_append_multiple(self, log):
        for i in range(5):
            log.append(ConversationAppendRequest(
                session_id="s1", role="user", content=f"msg {i}",
            ))
        assert log.count() == 5

    def test_append_with_metadata(self, log):
        req = ConversationAppendRequest(
            session_id="s1", role="tool_use", content="Read",
            event_type="ToolUseEvent", model="opus",
            metadata={"tool_id": "abc123", "input": "/home/file.py"},
        )
        log.append(req)
        entries = log.get_session("s1")
        assert len(entries) == 1
        assert entries[0].metadata == {"tool_id": "abc123", "input": "/home/file.py"}
        assert entries[0].event_type == "ToolUseEvent"

    def test_append_preserves_timestamp(self, log):
        ts = 1700000000.0
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="test", timestamp=ts,
        ))
        entries = log.get_session("s1")
        assert entries[0].timestamp == ts

    def test_append_auto_timestamp(self, log):
        before = time.time()
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="test",
        ))
        after = time.time()
        entries = log.get_session("s1")
        assert before <= entries[0].timestamp <= after


class TestConversationLogQuery:
    def test_get_session_ordered(self, log):
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="first", timestamp=1.0,
        ))
        log.append(ConversationAppendRequest(
            session_id="s1", role="assistant", content="second", timestamp=2.0,
        ))
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="third", timestamp=3.0,
        ))
        entries = log.get_session("s1")
        assert len(entries) == 3
        assert entries[0].content == "first"
        assert entries[1].content == "second"
        assert entries[2].content == "third"

    def test_get_session_isolates_sessions(self, log):
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="s1 msg",
        ))
        log.append(ConversationAppendRequest(
            session_id="s2", role="user", content="s2 msg",
        ))
        entries = log.get_session("s1")
        assert len(entries) == 1
        assert entries[0].content == "s1 msg"

    def test_get_recent(self, log):
        for i in range(10):
            log.append(ConversationAppendRequest(
                session_id="s1", role="user", content=f"msg {i}",
                timestamp=float(i + 1),  # 1..10 to avoid 0
            ))
        recent = log.get_recent(3)
        assert len(recent) == 3
        # Should be in chronological order (oldest first of the recent 3)
        assert recent[0].content == "msg 7"
        assert recent[1].content == "msg 8"
        assert recent[2].content == "msg 9"

    def test_get_sessions_list(self, log):
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="a", timestamp=1.0,
        ))
        log.append(ConversationAppendRequest(
            session_id="s1", role="assistant", content="b", timestamp=2.0,
        ))
        log.append(ConversationAppendRequest(
            session_id="s2", role="user", content="c", timestamp=3.0,
        ))
        sessions = log.get_sessions()
        assert len(sessions) == 2
        # Most recent session first
        assert sessions[0]["session_id"] == "s2"
        assert sessions[0]["entries"] == 1
        assert sessions[1]["session_id"] == "s1"
        assert sessions[1]["entries"] == 2

    def test_empty_session_returns_empty(self, log):
        assert log.get_session("nonexistent") == []

    def test_all_roles_stored(self, log):
        roles = ["user", "assistant", "tool_use", "tool_result", "system", "error"]
        for role in roles:
            log.append(ConversationAppendRequest(
                session_id="s1", role=role, content=f"{role} content",
            ))
        entries = log.get_session("s1")
        assert [e.role for e in entries] == roles


# --- Phase 6: digested column + get_undigested ---


@pytest.fixture
def log_with_data(tmp_path):
    db = str(tmp_path / "log.db")
    clog = ConversationLog(db)
    for i in range(5):
        clog.append(ConversationAppendRequest(
            session_id="sess-aaa", role="user" if i % 2 == 0 else "assistant",
            content=f"message {i}",
        ))
    for i in range(3):
        clog.append(ConversationAppendRequest(
            session_id="sess-bbb", role="user" if i % 2 == 0 else "assistant",
            content=f"other {i}",
        ))
    return clog


class TestDigestedColumn:
    def test_migration_adds_digested_column(self, log):
        """After init, digested column exists."""
        cols = log._conn.execute("PRAGMA table_info(conversation_log)").fetchall()
        col_names = [c[1] for c in cols]
        assert "digested" in col_names

    def test_new_entries_default_undigested(self, log):
        """New entries have digested=0."""
        log.append(ConversationAppendRequest(
            session_id="s1", role="user", content="test",
        ))
        row = log._conn.execute("SELECT digested FROM conversation_log").fetchone()
        assert row[0] == 0


class TestGetUndigested:
    def test_returns_only_undigested(self, log_with_data):
        """get_undigested() returns all entries (all are undigested)."""
        entries = log_with_data.get_undigested()
        assert len(entries) == 8

    def test_excludes_digested(self, log_with_data):
        """After marking some digested, get_undigested() excludes them."""
        entries = log_with_data.get_undigested()
        log_with_data.mark_digested([entries[0].id, entries[1].id])
        remaining = log_with_data.get_undigested()
        assert len(remaining) == 6
        remaining_ids = {e.id for e in remaining}
        assert entries[0].id not in remaining_ids
        assert entries[1].id not in remaining_ids

    def test_grouped_by_session(self, log_with_data):
        """get_undigested_by_session() groups by session_id."""
        grouped = log_with_data.get_undigested_by_session()
        assert isinstance(grouped, dict)
        assert "sess-aaa" in grouped
        assert "sess-bbb" in grouped
        assert len(grouped["sess-aaa"]) == 5
        assert len(grouped["sess-bbb"]) == 3
        for sid, entries in grouped.items():
            assert all(e.session_id == sid for e in entries)

    def test_mark_digested(self, log_with_data):
        """mark_digested() marks entries as processed."""
        entries = log_with_data.get_undigested()
        ids = [e.id for e in entries[:3]]
        log_with_data.mark_digested(ids)
        remaining = log_with_data.get_undigested()
        for e in remaining:
            assert e.id not in ids

    def test_mark_digested_empty_list(self, log_with_data):
        """mark_digested([]) is a no-op."""
        log_with_data.mark_digested([])
        entries = log_with_data.get_undigested()
        assert len(entries) == 8

    def test_reset_digested(self, log_with_data):
        """reset_digested() marks all entries as undigested."""
        entries = log_with_data.get_undigested()
        log_with_data.mark_digested([e.id for e in entries])
        assert len(log_with_data.get_undigested()) == 0
        n = log_with_data.reset_digested()
        assert n == 8
        assert len(log_with_data.get_undigested()) == 8

    def test_snapshot_all_undigested(self, tmp_path):
        """Real DB snapshot — migration adds digested column — everything is undigested."""
        db = str(tmp_path / "log.db")
        shutil.copy("tests/fixtures/conversation_log_snapshot.db", db)
        log = ConversationLog(db)
        entries = log.get_undigested()
        assert len(entries) == 500  # default limit
        all_entries = log.get_undigested(limit=1000)
        assert len(all_entries) == 901
