# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# Licensed under the MIT License

"""Tests for Auto-Curate v2 — consolidation priority over heuristic (Sprint 9)."""

import threading
import time
from unittest.mock import MagicMock, patch

import pytest

from familiar.core.memory_tree import MemoryTree


@pytest.fixture
def tree(tmp_path):
    return MemoryTree(db_path=tmp_path / "autocurate_test.db")


class TestAutoConsolidateAsync:
    def test_async_stores_facts(self, tree):
        """auto_consolidate_async should extract and store facts."""
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            from familiar.core.memory_consolidation_v2 import auto_consolidate_async

            auto_consolidate_async(
                user_message="My name is George and I live in Portland",
                assistant_response="Nice to meet you, George! Portland is a great city." * 3,
                user_id="default",
                conversation_id="test-conv",
            )
            # Wait for daemon thread
            time.sleep(0.5)

        entries = tree.list_by_tier("working")
        assert len(entries) >= 1

    def test_async_gates_short_response(self, tree):
        """Short responses should be skipped."""
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            from familiar.core.memory_consolidation_v2 import auto_consolidate_async

            auto_consolidate_async(
                user_message="My name is George",
                assistant_response="Hi!",  # Too short (< 100 chars)
                user_id="default",
            )
            time.sleep(0.3)

        entries = tree.list_by_tier("working")
        assert len(entries) == 0

    def test_async_gates_short_message(self, tree):
        """Short user messages should be skipped."""
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            from familiar.core.memory_consolidation_v2 import auto_consolidate_async

            auto_consolidate_async(
                user_message="Hi",  # Too short (< 10 chars)
                assistant_response="Hello! How can I help you today?" * 5,
                user_id="default",
            )
            time.sleep(0.3)

        entries = tree.list_by_tier("working")
        assert len(entries) == 0


class TestConsolidationPriority:
    """Test that consolidation_v2 takes priority over legacy auto_curate_check."""

    def test_tree_success_skips_legacy(self):
        """When MemoryTree consolidation succeeds, legacy curation should NOT run."""
        with (
            patch("familiar.core.memory_consolidation_v2.auto_consolidate_async") as mock_tree,
            patch("familiar.core.markdown_memory.auto_curate_check") as mock_legacy,
        ):
            # Simulate the agent code path
            _tree_ok = False
            try:
                mock_tree(
                    user_message="test",
                    assistant_response="response",
                    user_id="default",
                    conversation_id="c1",
                )
                _tree_ok = True
            except Exception:
                pass

            if not _tree_ok:
                mock_legacy(
                    user_message="test",
                    assistant_response="response",
                    user_id="default",
                    conversation_id="c1",
                )

            mock_tree.assert_called_once()
            mock_legacy.assert_not_called()

    def test_tree_failure_falls_back_to_legacy(self):
        """When MemoryTree consolidation fails, legacy curation should run."""
        with (
            patch(
                "familiar.core.memory_consolidation_v2.auto_consolidate_async",
                side_effect=ImportError("not available"),
            ) as mock_tree,
            patch("familiar.core.markdown_memory.auto_curate_check") as mock_legacy,
        ):
            _tree_ok = False
            try:
                mock_tree(
                    user_message="test",
                    assistant_response="response",
                    user_id="default",
                    conversation_id="c1",
                )
                _tree_ok = True
            except Exception:
                pass

            if not _tree_ok:
                mock_legacy(
                    user_message="test",
                    assistant_response="response",
                    user_id="default",
                    conversation_id="c1",
                )

            mock_tree.assert_called_once()
            mock_legacy.assert_called_once()

    def test_consolidation_idempotent(self, tree):
        """Running consolidation twice on same input should not duplicate."""
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            from familiar.core.memory_consolidation_v2 import consolidate_exchange

            stats1 = consolidate_exchange(
                user_message="My name is George",
                assistant_response="Got it!" * 20,
                user_id="default",
            )
            stats2 = consolidate_exchange(
                user_message="My name is George",
                assistant_response="I know!" * 20,
                user_id="default",
            )

        assert stats1["add"] >= 1
        assert stats2["add"] == 0  # Should NOOP on second run

    def test_consolidation_updates_on_change(self, tree):
        """Changed facts should trigger UPDATE, not ADD."""
        with patch("familiar.core.memory_tree.get_memory_tree", return_value=tree):
            from familiar.core.memory_consolidation_v2 import consolidate_exchange

            consolidate_exchange(
                user_message="I live in Portland Oregon near the river",
                assistant_response="Portland is great!" * 10,
                user_id="default",
            )
            stats = consolidate_exchange(
                user_message="I live in Seattle Washington on Capitol Hill",
                assistant_response="Seattle, nice!" * 10,
                user_id="default",
            )

        # Should detect the location change
        total = stats["update"] + stats["add"]
        assert total >= 1
