"""
Tests for insa_its.prompts.task_anchor
======================================
Validates TaskAnchorManager: file loading, hot-reload, injection building,
and task boundary detection heuristics.
"""

from __future__ import annotations

import os
import tempfile
import time
from pathlib import Path
from typing import Optional
from unittest.mock import patch

import pytest

from insa_its.prompts.task_anchor import (
    DEFAULT_ANCHOR_FILE,
    DEFAULT_MEMORY_CHECK_FILE,
    NEW_TASK_GAP_SECONDS,
    TaskAnchorManager,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def tmp_anchor_file(tmp_path: Path) -> Path:
    """Create a temporary anchor file with default content."""
    f = tmp_path / "user_anchor.md"
    f.write_text("Test anchor content.", encoding="utf-8")
    return f


@pytest.fixture
def tmp_memory_file(tmp_path: Path) -> Path:
    """Create a temporary memory check file."""
    f = tmp_path / "memory_check.md"
    f.write_text("Test memory check.", encoding="utf-8")
    return f


@pytest.fixture
def manager(tmp_anchor_file: Path, tmp_memory_file: Path) -> TaskAnchorManager:
    """TaskAnchorManager with temp files."""
    return TaskAnchorManager(
        anchor_file=str(tmp_anchor_file),
        memory_check_file=str(tmp_memory_file),
    )


# ---------------------------------------------------------------------------
# test_default_anchor_text_loaded
# ---------------------------------------------------------------------------

class TestDefaultAnchorTextLoaded:
    """Verify the default anchor file ships with expected content."""

    def test_default_anchor_text_loaded(self) -> None:
        mgr = TaskAnchorManager()
        text = mgr.get_anchor_text()
        assert "insaits" in text.lower()
        assert "task boundary" in text.lower()
        assert "security" in text.lower()
        assert len(text) > 100  # real content, not a placeholder


# ---------------------------------------------------------------------------
# test_custom_anchor_file
# ---------------------------------------------------------------------------

class TestCustomAnchorFile:
    """Loading anchor text from a custom file path."""

    def test_custom_anchor_file(self, tmp_path: Path) -> None:
        custom = tmp_path / "my_anchor.md"
        custom.write_text("My custom anchor instructions.", encoding="utf-8")
        mgr = TaskAnchorManager(anchor_file=str(custom))
        assert mgr.get_anchor_text() == "My custom anchor instructions."

    def test_missing_file_returns_empty(self, tmp_path: Path) -> None:
        mgr = TaskAnchorManager(anchor_file=str(tmp_path / "nonexistent.md"))
        assert mgr.get_anchor_text() == ""


# ---------------------------------------------------------------------------
# test_hot_reload_on_file_change
# ---------------------------------------------------------------------------

class TestHotReloadOnFileChange:
    """Anchor text re-reads when file mtime changes."""

    def test_hot_reload_on_file_change(self, tmp_anchor_file: Path) -> None:
        mgr = TaskAnchorManager(anchor_file=str(tmp_anchor_file))

        # First read
        text_v1 = mgr.get_anchor_text()
        assert text_v1 == "Test anchor content."

        # Modify file — bump mtime to ensure OS sees it as changed
        time.sleep(0.05)
        tmp_anchor_file.write_text("Updated anchor v2.", encoding="utf-8")
        # Force mtime difference on fast filesystems
        new_mtime = os.path.getmtime(str(tmp_anchor_file)) + 1
        os.utime(str(tmp_anchor_file), (new_mtime, new_mtime))

        text_v2 = mgr.get_anchor_text()
        assert text_v2 == "Updated anchor v2."
        assert text_v2 != text_v1

    def test_no_reload_when_unchanged(self, tmp_anchor_file: Path) -> None:
        mgr = TaskAnchorManager(anchor_file=str(tmp_anchor_file))
        text_v1 = mgr.get_anchor_text()
        text_v2 = mgr.get_anchor_text()
        assert text_v1 == text_v2


# ---------------------------------------------------------------------------
# test_memory_check_text
# ---------------------------------------------------------------------------

class TestMemoryCheckText:
    """Memory check file loading."""

    def test_memory_check_text(self) -> None:
        mgr = TaskAnchorManager()
        text = mgr.get_memory_check_text()
        assert "memory" in text.lower()
        assert "preferences" in text.lower()

    def test_custom_memory_check_file(self, tmp_path: Path) -> None:
        custom = tmp_path / "custom_mem.md"
        custom.write_text("Custom memory instructions.", encoding="utf-8")
        mgr = TaskAnchorManager(memory_check_file=str(custom))
        assert mgr.get_memory_check_text() == "Custom memory instructions."


# ---------------------------------------------------------------------------
# test_build_injection_both_enabled
# ---------------------------------------------------------------------------

class TestBuildInjectionBothEnabled:
    """Combined injection text with both sources enabled."""

    def test_build_injection_both_enabled(self, manager: TaskAnchorManager) -> None:
        injection = manager.build_injection()
        assert "[USER ANCHOR]" in injection
        assert "Test anchor content." in injection
        assert "[MEMORY CHECK]" in injection
        assert "Test memory check." in injection

    def test_sections_separated_by_blank_line(self, manager: TaskAnchorManager) -> None:
        injection = manager.build_injection()
        assert "\n\n" in injection


# ---------------------------------------------------------------------------
# test_build_injection_anchor_only
# ---------------------------------------------------------------------------

class TestBuildInjectionAnchorOnly:
    """Injection text with memory check disabled."""

    def test_build_injection_anchor_only(
        self, tmp_anchor_file: Path, tmp_memory_file: Path
    ) -> None:
        mgr = TaskAnchorManager(
            anchor_file=str(tmp_anchor_file),
            memory_check_file=str(tmp_memory_file),
            memory_check_enabled=False,
        )
        injection = mgr.build_injection()
        assert "[USER ANCHOR]" in injection
        assert "Test anchor content." in injection
        assert "[MEMORY CHECK]" not in injection


# ---------------------------------------------------------------------------
# test_build_injection_memory_check_disabled
# ---------------------------------------------------------------------------

class TestBuildInjectionMemoryCheckDisabled:
    """Memory check disabled via config."""

    def test_build_injection_memory_check_disabled(
        self, tmp_anchor_file: Path, tmp_memory_file: Path
    ) -> None:
        mgr = TaskAnchorManager(
            anchor_file=str(tmp_anchor_file),
            memory_check_file=str(tmp_memory_file),
            memory_check_enabled=False,
        )
        injection = mgr.build_injection()
        assert "[MEMORY CHECK]" not in injection
        assert "Test memory check." not in injection

    def test_disable_memory_check_at_runtime(
        self, manager: TaskAnchorManager
    ) -> None:
        # Initially both enabled
        assert "[MEMORY CHECK]" in manager.build_injection()
        # Disable at runtime
        manager.memory_check_enabled = False
        assert "[MEMORY CHECK]" not in manager.build_injection()


# ---------------------------------------------------------------------------
# test_build_injection_both_disabled_returns_empty
# ---------------------------------------------------------------------------

class TestBuildInjectionBothDisabledReturnsEmpty:
    """When fully disabled, returns empty string."""

    def test_build_injection_both_disabled_returns_empty(
        self, tmp_anchor_file: Path, tmp_memory_file: Path
    ) -> None:
        mgr = TaskAnchorManager(
            anchor_file=str(tmp_anchor_file),
            memory_check_file=str(tmp_memory_file),
            enabled=False,
        )
        assert mgr.build_injection() == ""

    def test_disable_at_runtime(self, manager: TaskAnchorManager) -> None:
        assert manager.build_injection() != ""
        manager.enabled = False
        assert manager.build_injection() == ""


# ---------------------------------------------------------------------------
# test_is_new_task_detects_new_task
# ---------------------------------------------------------------------------

class TestIsNewTaskDetectsNewTask:
    """Heuristic correctly identifies new task boundaries."""

    def test_first_call_is_always_new_task(self, manager: TaskAnchorManager) -> None:
        assert manager.is_new_task("Bash", {}, None) is True

    def test_time_gap_triggers_new_task(self, manager: TaskAnchorManager) -> None:
        # First call
        manager.is_new_task("Bash", {}, None)

        # Simulate time gap by backdating _last_tool_time
        manager._last_tool_time = time.time() - (NEW_TASK_GAP_SECONDS + 10)

        assert manager.is_new_task("Read", {}, "Bash") is True

    def test_none_to_tool_transition_is_new_task(
        self, manager: TaskAnchorManager
    ) -> None:
        # First call
        manager.is_new_task("Bash", {}, None)
        # Set previous to None (simulating non-tool state)
        manager._last_tool_name = None
        manager._last_tool_time = time.time()  # recent, so no gap

        assert manager.is_new_task("Read", {}, None) is True


# ---------------------------------------------------------------------------
# test_is_new_task_continuation_returns_false
# ---------------------------------------------------------------------------

class TestIsNewTaskContinuationReturnsFalse:
    """Heuristic correctly identifies mid-task continuation."""

    def test_sequential_tools_within_gap_is_continuation(
        self, manager: TaskAnchorManager
    ) -> None:
        # First call — always new
        assert manager.is_new_task("Bash", {}, None) is True
        # Immediate second call — continuation
        assert manager.is_new_task("Read", {}, "Bash") is False

    def test_same_tool_repeated_is_continuation(
        self, manager: TaskAnchorManager
    ) -> None:
        manager.is_new_task("Bash", {}, None)
        assert manager.is_new_task("Bash", {}, "Bash") is False

    def test_multiple_rapid_calls_stay_continuation(
        self, manager: TaskAnchorManager
    ) -> None:
        manager.is_new_task("Bash", {}, None)
        for tool in ["Read", "Edit", "Write", "Bash"]:
            assert manager.is_new_task(tool, {}, None) is False
