"""Property-based tests for pattern-only suppression.

Feature: competitive-historical-patterns

Uses Hypothesis to validate correctness properties of the pattern-only
suppression logic in the recommendation service.
"""
from __future__ import annotations

from hypothesis import given, settings
from hypothesis import strategies as st

from services.recommendation.suppression import (
    PATTERN_ONLY_CAVEAT,
    evaluate_pattern_only_suppression,
)
from services.shared.schemas import TrendDirection, TrendSummary, TrendWindow

# ---------------------------------------------------------------------------
# Hypothesis strategies
# ---------------------------------------------------------------------------

def _minimal_trend_summary() -> st.SearchStrategy[TrendSummary]:
    """Generate a minimal TrendSummary with random direction and window."""
    return st.builds(
        TrendSummary,
        entity_id=st.text(
            alphabet=st.characters(whitelist_categories=("Lu",)),
            min_size=1,
            max_size=5,
        ),
        window=st.sampled_from(list(TrendWindow)),
        trend_direction=st.sampled_from(list(TrendDirection)),
        confidence=st.floats(min_value=0.0, max_value=1.0, allow_nan=False),
    )


# ---------------------------------------------------------------------------
# Property 18: Pattern-only suppression
# ---------------------------------------------------------------------------


class TestProperty18PatternOnlySuppression:
    """Feature: competitive-historical-patterns, Property 18: Pattern-only suppression

    For any trend summary where the trend direction is driven solely by
    pattern-based and competitive signals (no company-specific or macro
    signals support the direction), the resulting recommendation SHALL have
    mode = 'informational' and the thesis SHALL contain a pattern-only caveat.

    **Validates: Requirements 9.3**
    """

    @given(
        summary=_minimal_trend_summary(),
        pattern_signal_count=st.integers(min_value=1, max_value=100),
    )
    @settings(max_examples=100)
    def test_pattern_only_signals_trigger_suppression(
        self,
        summary: TrendSummary,
        pattern_signal_count: int,
    ):
        """**Validates: Requirements 9.3**

        When pattern_signal_count > 0 AND company_signal_count == 0 AND
        macro_signal_count == 0, suppression must be triggered (returns True).
        """
        result = evaluate_pattern_only_suppression(
            summary=summary,
            pattern_signal_count=pattern_signal_count,
            company_signal_count=0,
            macro_signal_count=0,
        )
        assert result is True, (
            f"Expected suppression for pattern_only scenario "
            f"(pattern={pattern_signal_count}, company=0, macro=0), got False"
        )

    @given(
        summary=_minimal_trend_summary(),
        pattern_signal_count=st.integers(min_value=0, max_value=100),
        company_signal_count=st.integers(min_value=1, max_value=100),
        macro_signal_count=st.integers(min_value=0, max_value=100),
    )
    @settings(max_examples=100)
    def test_company_signals_prevent_suppression(
        self,
        summary: TrendSummary,
        pattern_signal_count: int,
        company_signal_count: int,
        macro_signal_count: int,
    ):
        """**Validates: Requirements 9.3**

        When company_signal_count > 0, suppression must NOT be triggered
        regardless of pattern or macro signal counts.
        """
        result = evaluate_pattern_only_suppression(
            summary=summary,
            pattern_signal_count=pattern_signal_count,
            company_signal_count=company_signal_count,
            macro_signal_count=macro_signal_count,
        )
        assert result is False, (
            f"Expected no suppression when company_signal_count={company_signal_count} > 0, "
            f"got True"
        )

    @given(
        summary=_minimal_trend_summary(),
        pattern_signal_count=st.integers(min_value=0, max_value=100),
        macro_signal_count=st.integers(min_value=1, max_value=100),
    )
    @settings(max_examples=100)
    def test_macro_signals_prevent_suppression(
        self,
        summary: TrendSummary,
        pattern_signal_count: int,
        macro_signal_count: int,
    ):
        """**Validates: Requirements 9.3**

        When macro_signal_count > 0 (and company_signal_count == 0),
        suppression must NOT be triggered regardless of pattern count.
        """
        result = evaluate_pattern_only_suppression(
            summary=summary,
            pattern_signal_count=pattern_signal_count,
            company_signal_count=0,
            macro_signal_count=macro_signal_count,
        )
        assert result is False, (
            f"Expected no suppression when macro_signal_count={macro_signal_count} > 0, "
            f"got True"
        )

    @given(
        summary=_minimal_trend_summary(),
        company_signal_count=st.integers(min_value=0, max_value=100),
        macro_signal_count=st.integers(min_value=0, max_value=100),
    )
    @settings(max_examples=100)
    def test_zero_pattern_signals_no_suppression(
        self,
        summary: TrendSummary,
        company_signal_count: int,
        macro_signal_count: int,
    ):
        """**Validates: Requirements 9.3**

        When pattern_signal_count == 0, suppression must NOT be triggered
        regardless of other signal counts.
        """
        result = evaluate_pattern_only_suppression(
            summary=summary,
            pattern_signal_count=0,
            company_signal_count=company_signal_count,
            macro_signal_count=macro_signal_count,
        )
        assert result is False, (
            "Expected no suppression when pattern_signal_count=0, got True"
        )

    def test_pattern_only_caveat_constant_exists(self):
        """**Validates: Requirements 9.3**

        The PATTERN_ONLY_CAVEAT constant must exist and contain expected
        key phrases for informational-mode recommendations.
        """
        assert isinstance(PATTERN_ONLY_CAVEAT, str)
        assert len(PATTERN_ONLY_CAVEAT) > 0
        assert "pattern" in PATTERN_ONLY_CAVEAT.lower()
        assert "informational" in PATTERN_ONLY_CAVEAT.lower()
