"""Tests for retrieval_enforcer.py.

Refs: Epic #367, Sub-issue #369
"""

from __future__ import annotations

import json
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))

from mempalace.retrieval_enforcer import (
    is_recall_query,
    load_identity,
    load_scratchpad,
    enforce_retrieval_order,
    search_skills,
    RECALL_PATTERNS,
)


class TestRecallDetection:
    """Test the recall-query pattern matcher."""

    @pytest.mark.parametrize("query", [
        "what did we work on yesterday",
        "status of the mempalace integration",
        "remember the fleet audit results",
        "last time we deployed the nexus",
        "previously you mentioned a CI fix",
        "we discussed the sovereign deployment",
    ])
    def test_recall_queries_detected(self, query):
        assert is_recall_query(query) is True

    @pytest.mark.parametrize("query", [
        "create a new file called test.py",
        "run the test suite",
        "deploy to production",
        "write a function that sums numbers",
        "install the package",
    ])
    def test_non_recall_queries_skipped(self, query):
        assert is_recall_query(query) is False


class TestLoadIdentity:
    def test_loads_existing_identity(self, tmp_path):
        identity_file = tmp_path / "identity.txt"
        identity_file.write_text("I am Timmy. A sovereign AI.")
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file):
            result = load_identity()
            assert "Timmy" in result

    def test_returns_empty_on_missing_file(self, tmp_path):
        identity_file = tmp_path / "nonexistent.txt"
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file):
            result = load_identity()
            assert result == ""

    def test_truncates_long_identity(self, tmp_path):
        identity_file = tmp_path / "identity.txt"
        identity_file.write_text(" ".join(["word"] * 300))
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file):
            result = load_identity()
            assert result.endswith("...")
            assert len(result.split()) <= 201  # 200 words + "..."


class TestLoadScratchpad:
    def test_loads_valid_scratchpad(self, tmp_path):
        scratch_file = tmp_path / "session123.json"
        scratch_file.write_text(json.dumps({"note": "test value", "key2": 42}))
        with patch("mempalace.retrieval_enforcer.SCRATCHPAD_DIR", tmp_path):
            result = load_scratchpad("session123")
            assert "note: test value" in result
            assert "key2: 42" in result

    def test_returns_empty_on_missing_file(self, tmp_path):
        with patch("mempalace.retrieval_enforcer.SCRATCHPAD_DIR", tmp_path):
            result = load_scratchpad("nonexistent")
            assert result == ""

    def test_returns_empty_on_invalid_json(self, tmp_path):
        scratch_file = tmp_path / "bad.json"
        scratch_file.write_text("not valid json{{{")
        with patch("mempalace.retrieval_enforcer.SCRATCHPAD_DIR", tmp_path):
            result = load_scratchpad("bad")
            assert result == ""


class TestEnforceRetrievalOrder:
    def test_skips_non_recall_query(self):
        result = enforce_retrieval_order("create a new file")
        assert result["retrieved_from"] is None
        assert result["tokens"] == 0

    def test_runs_for_recall_query(self, tmp_path):
        identity_file = tmp_path / "identity.txt"
        identity_file.write_text("I am Timmy.")
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file), \
             patch("mempalace.retrieval_enforcer.search_palace", return_value=""), \
             patch("mempalace.retrieval_enforcer.search_gitea", return_value=""), \
             patch("mempalace.retrieval_enforcer.search_skills", return_value=""):
            result = enforce_retrieval_order("what did we work on yesterday")
            assert "Identity" in result["context"]
            assert "L0" in result["layers_checked"]

    def test_palace_hit_sets_l1(self, tmp_path):
        identity_file = tmp_path / "identity.txt"
        identity_file.write_text("I am Timmy.")
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file), \
             patch("mempalace.retrieval_enforcer.search_palace", return_value="Found: fleet audit results"), \
             patch("mempalace.retrieval_enforcer.search_gitea", return_value=""):
            result = enforce_retrieval_order("what did we discuss yesterday")
            assert result["retrieved_from"] == "L1"
            assert "Palace Memory" in result["context"]

    def test_falls_through_to_l5(self, tmp_path):
        identity_file = tmp_path / "nonexistent.txt"
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file), \
             patch("mempalace.retrieval_enforcer.search_palace", return_value=""), \
             patch("mempalace.retrieval_enforcer.search_gitea", return_value=""), \
             patch("mempalace.retrieval_enforcer.search_skills", return_value=""):
            result = enforce_retrieval_order("remember the old deployment", skip_if_not_recall=True)
            assert result["retrieved_from"] == "L5"

    def test_force_mode_skips_recall_check(self, tmp_path):
        identity_file = tmp_path / "identity.txt"
        identity_file.write_text("I am Timmy.")
        with patch("mempalace.retrieval_enforcer.IDENTITY_PATH", identity_file), \
             patch("mempalace.retrieval_enforcer.search_palace", return_value=""), \
             patch("mempalace.retrieval_enforcer.search_gitea", return_value=""), \
             patch("mempalace.retrieval_enforcer.search_skills", return_value=""):
            result = enforce_retrieval_order("deploy now", skip_if_not_recall=False)
            assert "Identity" in result["context"]
