"""Tests for strategy cancellation checks (PR #2452).

Verifies:
- BaseSearchStrategy.check_termination() calls progress_callback
- check_termination() is called in each strategy's main loop
- ResearchTerminatedException inherits from BaseException (not Exception)
  so that ``except Exception`` blocks naturally let it propagate
"""

import inspect
import pytest
from unittest.mock import MagicMock

from local_deep_research.advanced_search_system.strategies.base_strategy import (
    BaseSearchStrategy,
)
from local_deep_research.exceptions import ResearchTerminatedException


class TestCheckTerminationMethod:
    """Verify check_termination() on BaseSearchStrategy."""

    def test_calls_progress_callback(self):
        """check_termination should call progress_callback with termination_check phase."""

        class ConcreteStrategy(BaseSearchStrategy):
            def analyze_topic(self, query):
                pass

        strategy = ConcreteStrategy.__new__(ConcreteStrategy)
        strategy.progress_callback = MagicMock()

        strategy.check_termination()

        strategy.progress_callback.assert_called_once()
        args = strategy.progress_callback.call_args
        assert args[0][2]["phase"] == "termination_check"

    def test_no_callback_no_error(self):
        """check_termination should not error if no callback set."""

        class ConcreteStrategy(BaseSearchStrategy):
            def analyze_topic(self, query):
                pass

        strategy = ConcreteStrategy.__new__(ConcreteStrategy)
        strategy.progress_callback = None

        # Should not raise
        strategy.check_termination()

    def test_propagates_callback_exception(self):
        """If callback raises ResearchTerminatedException, it should propagate."""

        class ConcreteStrategy(BaseSearchStrategy):
            def analyze_topic(self, query):
                pass

        strategy = ConcreteStrategy.__new__(ConcreteStrategy)
        strategy.progress_callback = MagicMock(
            side_effect=ResearchTerminatedException("Research terminated")
        )

        with pytest.raises(
            ResearchTerminatedException, match="Research terminated"
        ):
            strategy.check_termination()


class TestStrategiesCallCheckTermination:
    """Verify each strategy calls check_termination() in its main loop."""

    def test_iterative_reasoning_calls_check(self):
        from local_deep_research.advanced_search_system.strategies.iterative_reasoning_strategy import (
            IterativeReasoningStrategy,
        )

        source = inspect.getsource(IterativeReasoningStrategy.analyze_topic)
        assert "self.check_termination()" in source

    def test_iterative_refinement_calls_check(self):
        from local_deep_research.advanced_search_system.strategies.iterative_refinement_strategy import (
            IterativeRefinementStrategy,
        )

        source = inspect.getsource(IterativeRefinementStrategy.analyze_topic)
        assert "self.check_termination()" in source

    def test_source_based_calls_check(self):
        from local_deep_research.advanced_search_system.strategies.source_based_strategy import (
            SourceBasedSearchStrategy,
        )

        source = inspect.getsource(SourceBasedSearchStrategy.analyze_topic)
        assert "self.check_termination()" in source


class TestResearchTerminatedException:
    """Verify ResearchTerminatedException class properties."""

    def test_is_base_exception_subclass(self):
        """ResearchTerminatedException should be a subclass of BaseException, not Exception."""
        assert issubclass(ResearchTerminatedException, BaseException)
        assert not issubclass(ResearchTerminatedException, Exception)

    def test_distinguishable_from_generic(self):
        """isinstance should distinguish ResearchTerminatedException from generic Exception."""
        termination_exc = ResearchTerminatedException("cancelled")
        generic_exc = Exception("some error")

        assert isinstance(termination_exc, ResearchTerminatedException)
        assert isinstance(termination_exc, BaseException)
        assert not isinstance(termination_exc, Exception)
        assert not isinstance(generic_exc, ResearchTerminatedException)

    def test_except_exception_does_not_catch(self):
        """The core guarantee: except Exception blocks can't swallow termination."""
        with pytest.raises(ResearchTerminatedException):
            try:
                raise ResearchTerminatedException("cancelled")
            except Exception:
                pass  # Must NOT catch it


class TestTerminationCheckPhaseSkipsLogging:
    """Verify that termination_check phase is handled silently in research_service."""

    def test_research_service_handles_termination_check_phase(self):
        """research_service progress_callback should early-return for termination_check phase."""
        from local_deep_research.web.services import research_service

        source = inspect.getsource(research_service)
        # Verify the early return for termination_check phase exists
        assert 'metadata.get("phase") == "termination_check"' in source
