"""Tests for BigMind MCP server tools (src/server.py).

All tests run against an isolated temp database (autouse fixture in conftest.py).
Server functions are called directly — no MCP transport layer needed for unit tests.
The module-level init_db() in server.py runs once at import time (harmless, idempotent).
All DATA operations are redirected to the temp DB by the monkeypatched BIGMIND_DB_PATH.
"""
import json
import re
import pytest
from datetime import datetime, timezone, timedelta
from pathlib import Path

from server import (
    memory_start_session,
    memory_end_session,
    memory_flag_important,
    memory_get_context,
    memory_get_session_detail,
    memory_search_chunks,
    memory_list_sessions,
    memory_store_fact,
    memory_update_profile,
    memory_append_chunk,
    memory_get_stats,
    memory_vacuum,
    memory_get_instructions,
    memory_health_check,
    memory_export,
    memory_deprecate_fact,
    memory_add_hypothesis,
    memory_resolve_hypothesis,
    memory_list_hypotheses,
)
from bigmind import memory_store
from bigmind.db import db


# ── Helpers ────────────────────────────────────────────────────────────────────

def _start_and_get_id() -> str:
    """Call memory_start_session and return the session UUID."""
    result = memory_start_session()
    match = re.search(r"id: `([^`]+)`", result)
    assert match, f"Could not extract session id from:\n{result}"
    return match.group(1)


def _close(sid: str, headline: str = "Test session", topics: str = "test") -> str:
    """Convenience wrapper for memory_end_session."""
    return memory_end_session(
        session_id=sid,
        one_liner=headline,
        topics=topics,
        outcome="Test outcome",
        summary="Test summary narrative.",
    )


# ── memory_start_session ───────────────────────────────────────────────────────

class TestMemoryStartSession:
    def test_returns_started_confirmation(self, temp_db):
        result = memory_start_session()
        assert "BigMind session started" in result

    def test_returns_valid_session_id(self, temp_db):
        sid = _start_and_get_id()
        # UUIDs are 36 chars with 4 hyphens
        assert len(sid) == 36
        assert sid.count("-") == 4

    def test_returns_context_markdown(self, temp_db):
        result = memory_start_session()
        assert "🧠 BigMind Context" in result

    def test_creates_open_session_in_db(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        before = memory_store.get_open_sessions(user["id"])
        memory_start_session()
        after = memory_store.get_open_sessions(user["id"])
        assert len(after) == len(before) + 1

    def test_multiple_starts_create_multiple_sessions(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        memory_start_session()
        memory_start_session()
        open_sessions = memory_store.get_open_sessions(user["id"])
        assert len(open_sessions) == 2


# ── memory_end_session ─────────────────────────────────────────────────────────

class TestMemoryEndSession:
    def test_returns_confirmation_with_headline(self, temp_db):
        sid = _start_and_get_id()
        result = _close(sid, headline="My important session")
        assert "✅ Session closed" in result
        assert "My important session" in result

    def test_session_no_longer_open(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        sid = _start_and_get_id()
        _close(sid)
        open_sessions = memory_store.get_open_sessions(user["id"])
        assert all(s["id"] != sid for s in open_sessions)

    def test_session_appears_in_closed_list(self, temp_db):
        sid = _start_and_get_id()
        _close(sid, headline="Findable session")
        result = memory_list_sessions()
        assert "Findable session" in result

    def test_tier2_summary_saved(self, temp_db):
        sid = _start_and_get_id()
        memory_end_session(
            session_id=sid,
            one_liner="Narrative session",
            topics="test",
            outcome="Done",
            summary="The full story lives here.",
            key_facts="- Key insight one",
            code_refs="src/server.py",
        )
        detail = memory_store.get_session_detail(sid)
        assert detail is not None
        assert "full story" in detail["summary"]
        assert "Key insight one" in detail["key_facts"]
        assert "src/server.py" in detail["code_refs"]

    def test_importance_stored_correctly(self, temp_db):
        sid = _start_and_get_id()
        memory_end_session(
            session_id=sid, one_liner="High importance",
            topics="test", outcome="Done", summary="Summary", importance=9,
        )
        with db() as conn:
            row = conn.execute(
                "SELECT importance FROM sessions WHERE id=?", (sid,)
            ).fetchone()
        assert row["importance"] == 9

    def test_topics_stored(self, temp_db):
        sid = _start_and_get_id()
        _close(sid, topics="bigmind,sqlite,memory")
        result = memory_list_sessions()
        assert "bigmind" in result


# ── memory_flag_important ──────────────────────────────────────────────────────

class TestMemoryFlagImportant:
    def test_returns_tier3_confirmation(self, temp_db):
        sid = _start_and_get_id()
        result = memory_flag_important(
            session_id=sid,
            content="We decided to use SQLite",
            flag_reason="architectural decision",
        )
        assert "✅ Stored as Tier-3 memory chunk" in result

    def test_chunk_id_increments(self, temp_db):
        sid = _start_and_get_id()
        r1 = memory_flag_important(session_id=sid, content="First chunk")
        r2 = memory_flag_important(session_id=sid, content="Second chunk")
        id1 = int(re.search(r"id: (\d+)", r1).group(1))
        id2 = int(re.search(r"id: (\d+)", r2).group(1))
        assert id2 > id1

    def test_chunk_is_searchable_after_flagging(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(
            session_id=sid,
            content="PostgreSQL migration plan discussed",
        )
        result = memory_search_chunks("PostgreSQL")
        assert "PostgreSQL" in result

    def test_flag_reason_stored(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(
            session_id=sid,
            content="Some content",
            flag_reason="user preference",
        )
        user = memory_store.get_or_create_user("testuser")
        chunks = memory_store.search_chunks(user["id"], "Some content")
        assert chunks[0]["flag_reason"] == "user preference"

    def test_default_role_is_assistant(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="Default role check")
        user = memory_store.get_or_create_user("testuser")
        chunks = memory_store.search_chunks(user["id"], "Default role check")
        assert chunks[0]["role"] == "assistant"

    def test_custom_role_stored(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="User said this", role="user")
        user = memory_store.get_or_create_user("testuser")
        chunks = memory_store.search_chunks(user["id"], "User said this")
        assert chunks[0]["role"] == "user"


# ── memory_get_context ─────────────────────────────────────────────────────────

class TestMemoryGetContext:
    def test_returns_markdown(self, temp_db):
        result = memory_get_context()
        assert "🧠 BigMind Context" in result

    def test_does_not_create_new_session(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        before = memory_store.get_open_sessions(user["id"])
        memory_get_context()
        after = memory_store.get_open_sessions(user["id"])
        assert len(before) == len(after)

    def test_reflects_profile_changes(self, temp_db):
        memory_update_profile(role="Staff Engineer")
        result = memory_get_context()
        assert "Staff Engineer" in result

    def test_reflects_stored_facts(self, temp_db):
        memory_store_fact(category="codebase", fact="Uses FastMCP for all servers")
        result = memory_get_context()
        assert "Uses FastMCP for all servers" in result


# ── memory_get_session_detail ──────────────────────────────────────────────────

class TestMemoryGetSessionDetail:
    def test_returns_detail_for_session_with_summary(self, temp_db):
        sid = _start_and_get_id()
        memory_end_session(
            session_id=sid, one_liner="Detailed one",
            topics="test", outcome="Done",
            summary="The complete story is stored here.",
        )
        result = memory_get_session_detail(sid)
        assert "complete story" in result

    def test_returns_key_facts_when_present(self, temp_db):
        sid = _start_and_get_id()
        memory_end_session(
            session_id=sid, one_liner="With facts",
            topics="test", outcome="Done", summary="Summary",
            key_facts="- Decided on Python\n- Chose SQLite",
        )
        result = memory_get_session_detail(sid)
        assert "Decided on Python" in result

    def test_returns_code_refs_when_present(self, temp_db):
        sid = _start_and_get_id()
        memory_end_session(
            session_id=sid, one_liner="With refs",
            topics="test", outcome="Done", summary="Summary",
            code_refs="bigmind/db.py, src/server.py",
        )
        result = memory_get_session_detail(sid)
        assert "bigmind/db.py" in result

    def test_returns_error_for_nonexistent_session(self, temp_db):
        result = memory_get_session_detail("00000000-0000-0000-0000-000000000000")
        assert "No detailed summary found" in result

    def test_returns_error_for_session_without_tier2(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        sid = memory_store.create_session(user["id"])
        result = memory_get_session_detail(sid)
        assert "No detailed summary found" in result


# ── memory_search_chunks ───────────────────────────────────────────────────────

class TestMemorySearchChunks:
    def test_returns_matching_results(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="Chose WAL mode for SQLite concurrency")
        result = memory_search_chunks("WAL mode")
        assert "WAL mode" in result
        assert "Result 1" in result

    def test_returns_no_results_message_when_empty(self, temp_db):
        result = memory_search_chunks("xyzzy_term_that_will_never_exist_42")
        assert "No memory chunks found" in result

    def test_result_count_in_header(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="alpha beta gamma")
        result = memory_search_chunks("alpha beta")
        assert "1 result" in result

    def test_respects_limit(self, temp_db):
        sid = _start_and_get_id()
        for i in range(5):
            memory_flag_important(session_id=sid, content=f"unique chunk item {i}")
        result = memory_search_chunks("unique chunk item", limit=2)
        assert "2 results" in result

    def test_isolated_to_current_user(self, temp_db):
        """Chunks from a different user must not appear in search results."""
        user = memory_store.get_or_create_user("testuser")
        other_user = memory_store.get_or_create_user("otheruser")
        other_sid = memory_store.create_session(other_user["id"])
        memory_store.append_chunk(other_sid, other_user["id"], "user", "secret other data")
        result = memory_search_chunks("secret other data")
        assert "No memory chunks found" in result


# ── memory_list_sessions ───────────────────────────────────────────────────────

class TestMemoryListSessions:
    def test_lists_closed_sessions(self, temp_db):
        sid = _start_and_get_id()
        _close(sid, headline="Listed session headline")
        result = memory_list_sessions()
        assert "Listed session headline" in result

    def test_no_sessions_message_when_empty(self, temp_db):
        result = memory_list_sessions()
        assert "No sessions found" in result

    def test_open_sessions_not_in_list(self, temp_db):
        _start_and_get_id()  # open session — should NOT appear
        result = memory_list_sessions()
        assert "No sessions found" in result

    def test_topics_filter_includes_match(self, temp_db):
        sid = _start_and_get_id()
        _close(sid, headline="Python backend work", topics="python,backend")
        result = memory_list_sessions(topics_filter="python")
        assert "Python backend work" in result

    def test_topics_filter_excludes_non_match(self, temp_db):
        sid1 = _start_and_get_id()
        _close(sid1, headline="Python session", topics="python")
        sid2 = _start_and_get_id()
        _close(sid2, headline="Design session", topics="design,frontend")
        result = memory_list_sessions(topics_filter="python")
        assert "Python session" in result
        assert "Design session" not in result

    def test_topics_filter_no_match_returns_message(self, temp_db):
        sid = _start_and_get_id()
        _close(sid, topics="test")
        result = memory_list_sessions(topics_filter="nonexistenttopic")
        assert "No sessions found" in result

    def test_tier2_indicator_shown(self, temp_db):
        sid = _start_and_get_id()
        memory_end_session(
            session_id=sid, one_liner="With detail",
            topics="test", outcome="Done", summary="Full narrative.",
        )
        result = memory_list_sessions()
        assert "📄" in result


# ── memory_store_fact ──────────────────────────────────────────────────────────

class TestMemoryStoreFact:
    def test_returns_confirmation(self, temp_db):
        result = memory_store_fact(category="preference", fact="Prefers dark mode always")
        assert "✅ Fact stored" in result
        assert "preference" in result
        assert "Prefers dark mode always" in result

    def test_fact_appears_in_context(self, temp_db):
        memory_store_fact(category="codebase", fact="All servers use FastMCP pattern")
        result = memory_get_context()
        assert "All servers use FastMCP pattern" in result

    def test_category_shown_in_context(self, temp_db):
        memory_store_fact(category="constraint", fact="Must support Python 3.12+")
        result = memory_get_context()
        assert "[constraint]" in result

    def test_multiple_facts_all_shown(self, temp_db):
        memory_store_fact(category="preference", fact="Uses uv for packaging")
        memory_store_fact(category="decision", fact="Chose SQLite over DuckDB")
        result = memory_get_context()
        assert "Uses uv for packaging" in result
        assert "Chose SQLite over DuckDB" in result


# ── memory_update_profile ──────────────────────────────────────────────────────

class TestMemoryUpdateProfile:
    def test_returns_confirmation(self, temp_db):
        result = memory_update_profile(role="Senior Engineer")
        assert "✅ Identity profile updated" in result

    def test_role_appears_in_context(self, temp_db):
        memory_update_profile(role="Principal Engineer")
        result = memory_get_context()
        assert "Principal Engineer" in result

    def test_preferences_appear_in_context(self, temp_db):
        memory_update_profile(preferences="Python first, no unnecessary abstractions")
        result = memory_get_context()
        assert "Python first" in result

    def test_pinned_facts_appear_in_context(self, temp_db):
        memory_update_profile(pinned_facts="- Always uses uv\n- macOS only")
        result = memory_get_context()
        assert "Always uses uv" in result

    def test_partial_update_preserves_existing_fields(self, temp_db):
        memory_update_profile(role="Engineer", preferences="Concise code")
        memory_update_profile(pinned_facts="- New pinned fact")  # only update pinned_facts
        result = memory_get_context()
        assert "Engineer" in result          # role preserved
        assert "Concise code" in result      # preferences preserved
        assert "New pinned fact" in result   # new field added


# ── memory_append_chunk ────────────────────────────────────────────────────────

class TestMemoryAppendChunk:
    def test_returns_confirmation(self, temp_db):
        sid = _start_and_get_id()
        result = memory_append_chunk(session_id=sid, content="Manually saved content")
        assert "✅ Chunk stored" in result

    def test_chunk_is_searchable(self, temp_db):
        sid = _start_and_get_id()
        memory_append_chunk(session_id=sid, content="Manually appended important data")
        result = memory_search_chunks("Manually appended")
        assert "Manually appended" in result

    def test_flag_reason_stored(self, temp_db):
        sid = _start_and_get_id()
        memory_append_chunk(
            session_id=sid, content="Some important note",
            flag_reason="manual save by user",
        )
        user = memory_store.get_or_create_user("testuser")
        chunks = memory_store.search_chunks(user["id"], "important note")
        assert chunks[0]["flag_reason"] == "manual save by user"


# ── memory_get_stats ───────────────────────────────────────────────────────────

class TestMemoryGetStats:
    def test_returns_stats_markdown(self, temp_db):
        result = memory_get_stats()
        assert "📊 BigMind Stats" in result
        assert "Sessions" in result
        assert "Facts" in result
        assert "Database size" in result
        assert "Database path" in result

    def test_session_count_is_accurate(self, temp_db):
        sid = _start_and_get_id()
        _close(sid)
        result = memory_get_stats()
        assert "| Sessions | 1 |" in result

    def test_chunk_count_is_accurate(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="Counted chunk")
        result = memory_get_stats()
        assert "| Memory chunks (Tier 3) | 1 |" in result

    def test_fact_count_is_accurate(self, temp_db):
        memory_store_fact(category="test", fact="A counted fact")
        result = memory_get_stats()
        assert "| Facts | 1 |" in result


# ── memory_vacuum ──────────────────────────────────────────────────────────────

class TestMemoryVacuum:
    def test_returns_confirmation(self, temp_db):
        result = memory_vacuum(older_than_days=90)
        assert "✅ Removed" in result
        assert "chunk(s)" in result

    def test_removes_old_chunks(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        sid = memory_store.create_session(user["id"])
        old_date = (datetime.now(timezone.utc) - timedelta(days=100)).isoformat()
        with db() as conn:
            cur = conn.execute(
                """INSERT INTO conversation_chunks
                   (session_id, user_id, role, content, flag_reason, seq, created_at)
                   VALUES (?,?,?,?,?,?,?)""",
                (sid, user["id"], "user", "old stale content", "old", 1, old_date),
            )
            chunk_id = cur.lastrowid
            conn.execute(
                "INSERT INTO conversation_chunks_fts(rowid, content, flag_reason) VALUES(?,?,?)",
                (chunk_id, "old stale content", "old"),
            )
        result = memory_vacuum(older_than_days=90)
        assert "Removed 1 chunk(s)" in result

    def test_preserves_recent_chunks(self, temp_db):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="Very recent important thing")
        result = memory_vacuum(older_than_days=90)
        assert "Removed 0 chunk(s)" in result

    def test_old_chunk_not_searchable_after_vacuum(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        sid = memory_store.create_session(user["id"])
        old_date = (datetime.now(timezone.utc) - timedelta(days=100)).isoformat()
        with db() as conn:
            cur = conn.execute(
                """INSERT INTO conversation_chunks
                   (session_id, user_id, role, content, flag_reason, seq, created_at)
                   VALUES (?,?,?,?,?,?,?)""",
                (sid, user["id"], "user", "ancient secret data", "old", 1, old_date),
            )
            chunk_id = cur.lastrowid
            conn.execute(
                "INSERT INTO conversation_chunks_fts(rowid, content, flag_reason) VALUES(?,?,?)",
                (chunk_id, "ancient secret data", "old"),
            )
        memory_vacuum(older_than_days=90)
        result = memory_search_chunks("ancient secret data")
        assert "No memory chunks found" in result


# ── memory_get_instructions ────────────────────────────────────────────────────

class TestMemoryGetInstructions:
    def test_returns_string(self, temp_db):
        result = memory_get_instructions()
        assert isinstance(result, str)
        assert len(result) > 0

    def test_contains_start_session_directive(self, temp_db):
        result = memory_get_instructions()
        assert "memory_start_session" in result

    def test_contains_end_session_directive(self, temp_db):
        result = memory_get_instructions()
        assert "memory_end_session" in result

    def test_contains_mandatory_language(self, temp_db):
        result = memory_get_instructions()
        assert "Always" in result


# ── memory_health_check ────────────────────────────────────────────────────────

class TestMemoryHealthCheck:
    def test_returns_health_report_header(self, temp_db):
        result = memory_health_check()
        assert "🩺 BigMind Health Check" in result

    def test_fts_in_sync_shown_on_clean_db(self, temp_db):
        result = memory_health_check()
        assert "FTS index" in result
        assert "✅" in result

    def test_no_warnings_on_clean_db(self, temp_db):
        result = memory_health_check()
        assert "⚠️" not in result

    def test_flags_stale_facts(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        fid = memory_store.store_fact(user["id"], "codebase", "Stale old technology note")
        old_date = (datetime.now(timezone.utc) - timedelta(days=60)).isoformat()
        with db() as conn:
            conn.execute("UPDATE facts SET updated_at=? WHERE id=?", (old_date, fid))
        result = memory_health_check(stale_days=30)
        assert "Stale facts" in result
        assert "Stale old technology note" in result

    def test_fresh_facts_not_flagged_as_stale(self, temp_db):
        memory_store_fact(category="preference", fact="Very fresh preference")
        result = memory_health_check(stale_days=30)
        assert "Stale facts: 0" not in result
        # The "✅ Facts freshness" line should appear
        assert "Facts freshness" in result

    def test_flags_sessions_without_summary(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        sid = memory_store.create_session(user["id"])
        memory_store.close_session(sid, "Session with no narrative")
        result = memory_health_check()
        assert "Sessions without Tier-2 summary" in result

    def test_no_warning_when_all_sessions_have_summary(self, temp_db):
        sid = _start_and_get_id()
        _close(sid)  # _close calls memory_end_session which saves a Tier-2 summary
        result = memory_health_check()
        assert "Sessions without Tier-2 summary" not in result

    def test_flags_low_confidence_facts(self, temp_db):
        user = memory_store.get_or_create_user("testuser")
        memory_store.store_fact(user["id"], "codebase", "Uncertain technology choice", confidence=0.4)
        result = memory_health_check()
        assert "Low-confidence facts" in result
        assert "Uncertain technology choice" in result

    def test_open_sessions_listed(self, temp_db):
        _start_and_get_id()  # creates an open session
        result = memory_health_check()
        assert "Open sessions" in result

    def test_default_stale_days_is_30(self, temp_db):
        result = memory_health_check()
        # Either "30 days" in the stale line or the clean version
        assert "30" in result


# ── memory_export ──────────────────────────────────────────────────────────────

class TestMemoryExport:
    def test_returns_confirmation(self, temp_db, tmp_path):
        out = str(tmp_path / "test_export.json")
        result = memory_export(output_path=out)
        assert "✅" in result
        assert "BigMind memory exported" in result

    def test_shows_file_path_in_result(self, temp_db, tmp_path):
        out = str(tmp_path / "test_export.json")
        result = memory_export(output_path=out)
        assert out in result

    def test_file_created_on_disk(self, temp_db, tmp_path):
        out = str(tmp_path / "test_export.json")
        memory_export(output_path=out)
        assert Path(out).exists()

    def test_result_contains_count_rows(self, temp_db, tmp_path):
        out = str(tmp_path / "test_export.json")
        result = memory_export(output_path=out)
        assert "| **Facts** |" in result
        assert "| **Sessions** |" in result
        assert "| **Chunks (Tier 3)** |" in result
        assert "| **File size** |" in result

    def test_exports_facts(self, temp_db, tmp_path):
        memory_store_fact(category="preference", fact="Exported preference via tool")
        out = str(tmp_path / "test_export.json")
        memory_export(output_path=out)
        data = json.loads(Path(out).read_text())
        assert data["stats"]["facts_count"] == 1
        assert any("Exported preference via tool" in f["fact"] for f in data["facts"])

    def test_exports_sessions_and_summaries(self, temp_db, tmp_path):
        sid = _start_and_get_id()
        _close(sid, headline="Session for export")
        out = str(tmp_path / "test_export.json")
        memory_export(output_path=out)
        data = json.loads(Path(out).read_text())
        assert data["stats"]["sessions_count"] >= 1
        matches = [s for s in data["sessions"] if s.get("one_liner") == "Session for export"]
        assert len(matches) == 1

    def test_exports_chunks(self, temp_db, tmp_path):
        sid = _start_and_get_id()
        memory_flag_important(session_id=sid, content="Chunk to be exported")
        out = str(tmp_path / "test_export.json")
        memory_export(output_path=out)
        data = json.loads(Path(out).read_text())
        assert data["stats"]["chunks_count"] == 1
        assert any("Chunk to be exported" in c["content"] for c in data["conversation_chunks"])

    def test_valid_json_output(self, temp_db, tmp_path):
        out = str(tmp_path / "test_export.json")
        memory_export(output_path=out)
        # Should not raise
        data = json.loads(Path(out).read_text())
        assert isinstance(data, dict)

    def test_export_date_present(self, temp_db, tmp_path):
        out = str(tmp_path / "test_export.json")
        memory_export(output_path=out)
        data = json.loads(Path(out).read_text())
        assert "export_date" in data
        assert "bigmind_schema_version" in data
        assert isinstance(data["bigmind_schema_version"], int)


# ── memory_deprecate_fact ──────────────────────────────────────────────────────

class TestMemoryDeprecateFact:
    def test_returns_confirmation(self, temp_db):
        result_store = memory_store_fact(category="codebase", fact="Old stack fact")
        fid = int(re.search(r"id: (\d+)", result_store).group(1))
        result = memory_deprecate_fact(fact_id=fid, reason="Technology replaced")
        assert "✅" in result
        assert str(fid) in result

    def test_reason_shown_in_confirmation(self, temp_db):
        result_store = memory_store_fact(category="decision", fact="Used Gradle")
        fid = int(re.search(r"id: (\d+)", result_store).group(1))
        result = memory_deprecate_fact(fact_id=fid, reason="Switched to Maven")
        assert "Switched to Maven" in result

    def test_no_reason_still_succeeds(self, temp_db):
        result_store = memory_store_fact(category="preference", fact="Some old pref")
        fid = int(re.search(r"id: (\d+)", result_store).group(1))
        result = memory_deprecate_fact(fact_id=fid)
        assert "✅" in result

    def test_deprecated_fact_absent_from_context(self, temp_db):
        result_store = memory_store_fact(category="codebase", fact="Outdated deployment detail")
        fid = int(re.search(r"id: (\d+)", result_store).group(1))
        # Confirm it's in context before deprecation
        assert "Outdated deployment detail" in memory_get_context()
        # Deprecate it
        memory_deprecate_fact(fact_id=fid, reason="No longer true")
        # Must be gone from context
        assert "Outdated deployment detail" not in memory_get_context()

    def test_other_facts_unaffected_by_deprecation(self, temp_db):
        r1 = memory_store_fact(category="preference", fact="Keep this preference")
        r2 = memory_store_fact(category="codebase", fact="Drop this codebase note")
        fid_drop = int(re.search(r"id: (\d+)", r2).group(1))
        memory_deprecate_fact(fact_id=fid_drop)
        ctx = memory_get_context()
        assert "Keep this preference" in ctx
        assert "Drop this codebase note" not in ctx

    def test_nonexistent_fact_returns_error(self, temp_db):
        result = memory_deprecate_fact(fact_id=99999)
        assert "❌" in result
        assert "99999" in result

    def test_health_check_does_not_flag_deprecated_facts_as_stale(self, temp_db):
        """Deprecated facts should not surface as actionable stale warnings."""
        user = memory_store.get_or_create_user("testuser")
        fid = memory_store.store_fact(user["id"], "codebase", "Will be deprecated")
        from datetime import timedelta
        old_date = (datetime.now(timezone.utc) - timedelta(days=60)).isoformat()
        from bigmind.db import db as _db
        with _db() as conn:
            conn.execute("UPDATE facts SET updated_at=? WHERE id=?", (old_date, fid))
        memory_deprecate_fact(fact_id=fid, reason="Removed intentionally")
        result = memory_health_check(stale_days=30)
        assert "Will be deprecated" not in result


# ── memory_add_hypothesis / resolve / list ─────────────────────────────────────

class TestMemoryHypotheses:
    def test_add_returns_confirmation(self, temp_db):
        sid = _start_and_get_id()
        result = memory_add_hypothesis(
            session_id=sid,
            hypothesis="I believe the issue is in the connection pool",
        )
        assert "💭 Hypothesis recorded" in result
        assert "connection pool" in result

    def test_add_shows_confidence_percent(self, temp_db):
        sid = _start_and_get_id()
        result = memory_add_hypothesis(
            session_id=sid,
            hypothesis="High confidence belief",
            confidence=0.9,
        )
        assert "90%" in result

    def test_add_returns_id(self, temp_db):
        sid = _start_and_get_id()
        result = memory_add_hypothesis(session_id=sid, hypothesis="Some thought")
        assert re.search(r"id: \d+", result)

    def test_list_empty_returns_message(self, temp_db):
        result = memory_list_hypotheses()
        assert "No hypotheses found" in result

    def test_list_shows_open_hypotheses(self, temp_db):
        sid = _start_and_get_id()
        memory_add_hypothesis(session_id=sid, hypothesis="The cache is stale")
        result = memory_list_hypotheses()
        assert "The cache is stale" in result
        assert "💭" in result

    def test_list_filter_by_status(self, temp_db):
        sid = _start_and_get_id()
        memory_add_hypothesis(session_id=sid, hypothesis="Will be confirmed soon")
        memory_add_hypothesis(session_id=sid, hypothesis="Will stay open")
        user = memory_store.get_or_create_user("testuser")
        hyps = memory_store.list_hypotheses(user["id"])
        hid = next(h["id"] for h in hyps if "confirmed" in h["hypothesis"])
        memory_resolve_hypothesis(hypothesis_id=hid, status="confirmed", resolution="Yes it was true")
        open_result = memory_list_hypotheses(status="open")
        confirmed_result = memory_list_hypotheses(status="confirmed")
        assert "Will stay open" in open_result
        assert "Will be confirmed soon" not in open_result
        assert "Will be confirmed soon" in confirmed_result

    def test_resolve_confirmed_shows_checkmark(self, temp_db):
        sid = _start_and_get_id()
        result_add = memory_add_hypothesis(session_id=sid, hypothesis="Bug in serializer")
        hid = int(re.search(r"id: (\d+)", result_add).group(1))
        result = memory_resolve_hypothesis(
            hypothesis_id=hid,
            status="confirmed",
            resolution="Confirmed — null check was missing in BVV serializer",
        )
        assert "✅" in result
        assert "confirmed" in result

    def test_resolve_refuted_shows_cross(self, temp_db):
        sid = _start_and_get_id()
        result_add = memory_add_hypothesis(session_id=sid, hypothesis="Network latency")
        hid = int(re.search(r"id: (\d+)", result_add).group(1))
        result = memory_resolve_hypothesis(
            hypothesis_id=hid,
            status="refuted",
            resolution="Was a race condition, not network",
        )
        assert "❌" in result
        assert "refuted" in result

    def test_resolve_abandoned_shows_icon(self, temp_db):
        sid = _start_and_get_id()
        result_add = memory_add_hypothesis(session_id=sid, hypothesis="Might be cache")
        hid = int(re.search(r"id: (\d+)", result_add).group(1))
        result = memory_resolve_hypothesis(hypothesis_id=hid, status="abandoned")
        assert "🚫" in result
        assert "abandoned" in result

    def test_resolve_shows_resolution_text(self, temp_db):
        sid = _start_and_get_id()
        result_add = memory_add_hypothesis(session_id=sid, hypothesis="A theory")
        hid = int(re.search(r"id: (\d+)", result_add).group(1))
        result = memory_resolve_hypothesis(
            hypothesis_id=hid, status="confirmed", resolution="The theory held up"
        )
        assert "The theory held up" in result

    def test_resolve_invalid_status_returns_error(self, temp_db):
        sid = _start_and_get_id()
        result_add = memory_add_hypothesis(session_id=sid, hypothesis="Some belief")
        hid = int(re.search(r"id: (\d+)", result_add).group(1))
        result = memory_resolve_hypothesis(hypothesis_id=hid, status="maybe")
        assert "❌" in result

    def test_resolve_nonexistent_id_returns_error(self, temp_db):
        result = memory_resolve_hypothesis(hypothesis_id=99999, status="confirmed")
        assert "❌" in result
        assert "99999" in result

    def test_list_shows_resolution_when_resolved(self, temp_db):
        sid = _start_and_get_id()
        result_add = memory_add_hypothesis(session_id=sid, hypothesis="Root cause is threading")
        hid = int(re.search(r"id: (\d+)", result_add).group(1))
        memory_resolve_hypothesis(
            hypothesis_id=hid, status="confirmed",
            resolution="Thread-local storage was the culprit"
        )
        result = memory_list_hypotheses()
        assert "Thread-local storage was the culprit" in result

    def test_list_status_filter_no_match_returns_message(self, temp_db):
        sid = _start_and_get_id()
        memory_add_hypothesis(session_id=sid, hypothesis="Open thought")
        result = memory_list_hypotheses(status="confirmed")
        assert "No hypotheses found" in result





