"""Tests for search-time conflict detection among decision memories.

When search returns 2+ decision-category memories with high pairwise
similarity (cosine > CONFLICT_SIMILARITY_THRESHOLD), those memories
should be annotated with a conflicts_with field listing conflicting IDs.

This detects duplicate or near-duplicate decisions that may contradict
each other — e.g. the same decision recorded twice with minor wording changes.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from claude_engram.storage import CONFLICT_SIMILARITY_THRESHOLD, MemoryStorage


@pytest.fixture()
def storage(tmp_path: Path) -> MemoryStorage:
    """Create a MemoryStorage with a temp database."""
    db = tmp_path / "test.db"
    s = MemoryStorage(db_path=db)
    yield s
    s.close()


class TestConflictDetection:
    """Conflict detection among decision-category search results."""

    def test_no_conflicts_different_topics(self, storage: MemoryStorage) -> None:
        """Decisions on completely different topics should not conflict."""
        storage.store(
            content="We decided to use PostgreSQL as the main relational database",
            category="decision",
        )
        storage.store(
            content="The team will adopt Kubernetes for container orchestration",
            category="decision",
        )
        storage.store(
            content="All frontend components must use React with TypeScript",
            category="decision",
        )

        results = storage.search(query="technology decisions", category="decision", limit=10)
        for r in results:
            assert "conflicts_with" not in r, (
                f"Memory {r['id']} should not have conflicts — topics are unrelated"
            )

    def test_conflicts_detected_similar_decisions(self, storage: MemoryStorage) -> None:
        """Near-duplicate decisions (same topic, minor wording) should conflict."""
        # These are the same decision recorded twice with different wording.
        # Cosine similarity > 0.90 for sentence-transformers/all-MiniLM-L6-v2.
        id_a = storage.store(
            content="The deployment strategy will use blue-green deployment on AWS",
            category="decision",
        )
        id_b = storage.store(
            content="We will implement blue-green deployment strategy for AWS deployments",
            category="decision",
        )

        results = storage.search(
            query="deployment strategy decision", category="decision", limit=10
        )
        result_map = {r["id"]: r for r in results}

        assert id_a in result_map
        assert id_b in result_map

        a_conflicts = result_map[id_a].get("conflicts_with", [])
        b_conflicts = result_map[id_b].get("conflicts_with", [])

        assert id_b in a_conflicts and id_a in b_conflicts, (
            f"Expected bidirectional conflict between {id_a} and {id_b}. "
            f"a.conflicts_with={a_conflicts}, b.conflicts_with={b_conflicts}"
        )

    def test_conflicts_not_flagged_for_non_decision_categories(
        self, storage: MemoryStorage
    ) -> None:
        """Similar memories in non-decision categories should NOT get conflict flags."""
        storage.store(
            content="LESSON: The deployment strategy uses blue-green deployment on AWS",
            category="lesson",
        )
        storage.store(
            content="LESSON: We implement blue-green deployment strategy for AWS",
            category="lesson",
        )

        results = storage.search(query="deployment strategy", category="lesson", limit=10)
        for r in results:
            assert "conflicts_with" not in r, (
                f"Memory {r['id']} (category={r['category']}) should not have conflict "
                f"flags — conflict detection is only for decisions"
            )

    def test_conflicts_bidirectional(self, storage: MemoryStorage) -> None:
        """If A conflicts with B, then B must also conflict with A."""
        id_a = storage.store(
            content="DECISION: The API will use JWT tokens for authentication with RS256 signing",
            category="decision",
        )
        id_b = storage.store(
            content=(
                "DECISION: The API authentication will use JWT with RS256 "
                "algorithm for signing tokens"
            ),
            category="decision",
        )

        results = storage.search(
            query="API authentication JWT decision", category="decision", limit=10
        )
        result_map = {r["id"]: r for r in results}

        if id_a in result_map and id_b in result_map:
            a_conflicts = result_map[id_a].get("conflicts_with", [])
            b_conflicts = result_map[id_b].get("conflicts_with", [])
            if id_b in a_conflicts:
                assert id_a in b_conflicts, "Conflicts must be bidirectional"
            if id_a in b_conflicts:
                assert id_b in a_conflicts, "Conflicts must be bidirectional"

    def test_no_conflicts_with_single_decision(self, storage: MemoryStorage) -> None:
        """A single decision result cannot conflict with itself."""
        storage.store(
            content="We decided to use SQLite for local storage",
            category="decision",
        )

        results = storage.search(query="local storage decision", category="decision", limit=10)
        assert len(results) >= 1
        for r in results:
            assert "conflicts_with" not in r

    def test_threshold_constant_exists(self) -> None:
        """The threshold constant should be defined and be 0.80."""
        assert CONFLICT_SIMILARITY_THRESHOLD == 0.80


class TestConflictDetectionMixed:
    """Conflict detection when search returns mixed categories."""

    def test_mixed_results_only_flag_decisions(self, storage: MemoryStorage) -> None:
        """When results mix categories, only decisions get conflict checks."""
        storage.store(
            content="DECISION: Database migrations must use Alembic with auto-generation enabled",
            category="decision",
        )
        storage.store(
            content="DECISION: We decided to use Alembic with autogenerate for all DB migrations",
            category="decision",
        )
        storage.store(
            content="LESSON: Alembic migrations are easier with autogenerate turned on",
            category="lesson",
        )

        results = storage.search(query="Alembic migrations", limit=10)

        for r in results:
            if r["category"] != "decision":
                assert "conflicts_with" not in r, (
                    f"Non-decision memory {r['id']} should not have conflicts"
                )
