# tests/unit/services/memory/reflection/test_types.py
"""Tests for Memory Reflection types."""

from datetime import UTC, datetime, timedelta
from uuid import uuid4

from app.services.memory.reflection.types import (
    Anomaly,
    AnomalyType,
    Factor,
    FactorType,
    Insight,
    InsightType,
    Pattern,
    PatternType,
    ReflectionResult,
    TimeRange,
)


class TestTimeRange:
    """Tests for TimeRange."""

    def test_creates_time_range(self) -> None:
        """Should create time range with start and end."""
        start = datetime.now(UTC) - timedelta(days=7)
        end = datetime.now(UTC)

        tr = TimeRange(start=start, end=end)

        assert tr.start == start
        assert tr.end == end

    def test_last_hours(self) -> None:
        """Should create time range for last N hours."""
        tr = TimeRange.last_hours(24)

        assert tr.duration_hours >= 23.9
        assert tr.duration_hours <= 24.1

    def test_last_days(self) -> None:
        """Should create time range for last N days."""
        tr = TimeRange.last_days(7)

        assert tr.duration_days >= 6.9
        assert tr.duration_days <= 7.1

    def test_duration_hours(self) -> None:
        """Should calculate duration in hours."""
        start = datetime.now(UTC) - timedelta(hours=12)
        end = datetime.now(UTC)

        tr = TimeRange(start=start, end=end)

        assert 11.9 <= tr.duration_hours <= 12.1

    def test_duration_days(self) -> None:
        """Should calculate duration in days."""
        start = datetime.now(UTC) - timedelta(days=3)
        end = datetime.now(UTC)

        tr = TimeRange(start=start, end=end)

        assert 2.9 <= tr.duration_days <= 3.1


class TestPattern:
    """Tests for Pattern."""

    def test_creates_pattern(self) -> None:
        """Should create pattern with all fields."""
        now = datetime.now(UTC)
        episode_ids = [uuid4(), uuid4(), uuid4()]

        pattern = Pattern(
            id=uuid4(),
            pattern_type=PatternType.RECURRING_SUCCESS,
            name="Test Pattern",
            description="A test pattern",
            confidence=0.85,
            occurrence_count=10,
            episode_ids=episode_ids,
            first_seen=now - timedelta(days=7),
            last_seen=now,
        )

        assert pattern.name == "Test Pattern"
        assert pattern.confidence == 0.85
        assert len(pattern.episode_ids) == 3

    def test_frequency_calculation(self) -> None:
        """Should calculate frequency per day."""
        now = datetime.now(UTC)

        pattern = Pattern(
            id=uuid4(),
            pattern_type=PatternType.RECURRING_SUCCESS,
            name="Test",
            description="Test",
            confidence=0.8,
            occurrence_count=14,
            episode_ids=[],
            first_seen=now - timedelta(days=7),
            last_seen=now,
        )

        assert pattern.frequency == 2.0  # 14 occurrences / 7 days

    def test_frequency_minimum_one_day(self) -> None:
        """Should use minimum 1 day for frequency calculation."""
        now = datetime.now(UTC)

        pattern = Pattern(
            id=uuid4(),
            pattern_type=PatternType.RECURRING_SUCCESS,
            name="Test",
            description="Test",
            confidence=0.8,
            occurrence_count=5,
            episode_ids=[],
            first_seen=now - timedelta(hours=1),  # Less than 1 day
            last_seen=now,
        )

        assert pattern.frequency == 5.0  # 5 / 1 day minimum

    def test_to_dict(self) -> None:
        """Should convert to dictionary."""
        pattern = Pattern(
            id=uuid4(),
            pattern_type=PatternType.ACTION_SEQUENCE,
            name="Action Pattern",
            description="Action sequence",
            confidence=0.75,
            occurrence_count=5,
            episode_ids=[uuid4()],
            first_seen=datetime.now(UTC) - timedelta(days=1),
            last_seen=datetime.now(UTC),
            metadata={"key": "value"},
        )

        result = pattern.to_dict()

        assert result["name"] == "Action Pattern"
        assert result["pattern_type"] == "action_sequence"
        assert result["confidence"] == 0.75
        assert "frequency" in result
        assert result["metadata"] == {"key": "value"}


class TestFactor:
    """Tests for Factor."""

    def test_creates_factor(self) -> None:
        """Should create factor with all fields."""
        factor = Factor(
            id=uuid4(),
            factor_type=FactorType.ACTION,
            name="Test Factor",
            description="A test factor",
            impact_score=0.7,
            correlation=0.5,
            sample_size=20,
            positive_examples=[uuid4()],
            negative_examples=[uuid4()],
        )

        assert factor.name == "Test Factor"
        assert factor.impact_score == 0.7
        assert factor.correlation == 0.5

    def test_net_impact_calculation(self) -> None:
        """Should calculate net impact."""
        factor = Factor(
            id=uuid4(),
            factor_type=FactorType.CONTEXT,
            name="Test",
            description="Test",
            impact_score=0.8,
            correlation=0.6,
            sample_size=20,
            positive_examples=[],
            negative_examples=[],
        )

        # net_impact = impact_score * correlation * confidence_weight
        # confidence_weight = min(1.0, 20/20) = 1.0
        expected = 0.8 * 0.6 * 1.0
        assert factor.net_impact == expected

    def test_net_impact_with_small_sample(self) -> None:
        """Should weight net impact by sample size."""
        factor = Factor(
            id=uuid4(),
            factor_type=FactorType.CONTEXT,
            name="Test",
            description="Test",
            impact_score=0.8,
            correlation=0.6,
            sample_size=10,  # Half of 20
            positive_examples=[],
            negative_examples=[],
        )

        # confidence_weight = min(1.0, 10/20) = 0.5
        expected = 0.8 * 0.6 * 0.5
        assert factor.net_impact == expected

    def test_to_dict(self) -> None:
        """Should convert to dictionary."""
        factor = Factor(
            id=uuid4(),
            factor_type=FactorType.TIMING,
            name="Timing Factor",
            description="Time-related",
            impact_score=0.6,
            correlation=-0.3,
            sample_size=15,
            positive_examples=[],
            negative_examples=[],
            metadata={"key": "value"},
        )

        result = factor.to_dict()

        assert result["name"] == "Timing Factor"
        assert result["factor_type"] == "timing"
        assert "net_impact" in result
        assert result["metadata"] == {"key": "value"}


class TestAnomaly:
    """Tests for Anomaly."""

    def test_creates_anomaly(self) -> None:
        """Should create anomaly with all fields."""
        anomaly = Anomaly(
            id=uuid4(),
            anomaly_type=AnomalyType.UNUSUAL_DURATION,
            description="Unusual duration detected",
            severity=0.75,
            episode_ids=[uuid4()],
            detected_at=datetime.now(UTC),
            baseline_value=10.0,
            observed_value=30.0,
            deviation_factor=3.0,
        )

        assert anomaly.severity == 0.75
        assert anomaly.baseline_value == 10.0
        assert anomaly.deviation_factor == 3.0

    def test_is_critical_high_severity(self) -> None:
        """Should be critical when severity > 0.8."""
        anomaly = Anomaly(
            id=uuid4(),
            anomaly_type=AnomalyType.UNUSUAL_FAILURE_RATE,
            description="High failure rate",
            severity=0.9,
            episode_ids=[],
            detected_at=datetime.now(UTC),
            baseline_value=0.1,
            observed_value=0.5,
            deviation_factor=5.0,
        )

        assert anomaly.is_critical is True

    def test_is_critical_low_severity(self) -> None:
        """Should not be critical when severity <= 0.8."""
        anomaly = Anomaly(
            id=uuid4(),
            anomaly_type=AnomalyType.UNUSUAL_DURATION,
            description="Slightly unusual",
            severity=0.6,
            episode_ids=[],
            detected_at=datetime.now(UTC),
            baseline_value=10.0,
            observed_value=20.0,
            deviation_factor=2.0,
        )

        assert anomaly.is_critical is False

    def test_to_dict(self) -> None:
        """Should convert to dictionary."""
        anomaly = Anomaly(
            id=uuid4(),
            anomaly_type=AnomalyType.UNEXPECTED_OUTCOME,
            description="Unexpected failure",
            severity=0.85,
            episode_ids=[uuid4()],
            detected_at=datetime.now(UTC),
            baseline_value=0.9,
            observed_value=0.0,
            deviation_factor=0.9,
            metadata={"task_type": "test"},
        )

        result = anomaly.to_dict()

        assert result["anomaly_type"] == "unexpected_outcome"
        assert result["severity"] == 0.85
        assert result["is_critical"] is True
        assert result["metadata"] == {"task_type": "test"}


class TestInsight:
    """Tests for Insight."""

    def test_creates_insight(self) -> None:
        """Should create insight with all fields."""
        insight = Insight(
            id=uuid4(),
            insight_type=InsightType.OPTIMIZATION,
            title="Performance Opportunity",
            description="Optimization potential found",
            priority=0.8,
            confidence=0.75,
            source_patterns=[uuid4()],
            source_factors=[],
            source_anomalies=[],
            recommended_actions=["Action 1", "Action 2"],
            generated_at=datetime.now(UTC),
        )

        assert insight.title == "Performance Opportunity"
        assert insight.priority == 0.8
        assert len(insight.recommended_actions) == 2

    def test_actionable_score(self) -> None:
        """Should calculate actionable score."""
        insight = Insight(
            id=uuid4(),
            insight_type=InsightType.RECOMMENDATION,
            title="Test",
            description="Test",
            priority=0.8,
            confidence=0.9,
            source_patterns=[],
            source_factors=[],
            source_anomalies=[],
            recommended_actions=["Action 1", "Action 2", "Action 3"],
            generated_at=datetime.now(UTC),
        )

        # actionable_score = priority * confidence * action_weight
        # action_weight = min(1.0, 3/3) = 1.0
        expected = 0.8 * 0.9 * 1.0
        assert insight.actionable_score == expected

    def test_actionable_score_few_actions(self) -> None:
        """Should weight by action count."""
        insight = Insight(
            id=uuid4(),
            insight_type=InsightType.WARNING,
            title="Test",
            description="Test",
            priority=0.8,
            confidence=0.9,
            source_patterns=[],
            source_factors=[],
            source_anomalies=[],
            recommended_actions=["Action 1"],  # Only 1 action
            generated_at=datetime.now(UTC),
        )

        # action_weight = min(1.0, 1/3) = 0.333...
        expected = 0.8 * 0.9 * (1 / 3)
        assert abs(insight.actionable_score - expected) < 0.001

    def test_to_dict(self) -> None:
        """Should convert to dictionary."""
        insight = Insight(
            id=uuid4(),
            insight_type=InsightType.TREND,
            title="Trend Analysis",
            description="Performance trend",
            priority=0.6,
            confidence=0.7,
            source_patterns=[uuid4()],
            source_factors=[uuid4()],
            source_anomalies=[],
            recommended_actions=["Monitor", "Review"],
            generated_at=datetime.now(UTC),
            metadata={"health_score": 0.85},
        )

        result = insight.to_dict()

        assert result["insight_type"] == "trend"
        assert result["title"] == "Trend Analysis"
        assert "actionable_score" in result
        assert result["metadata"] == {"health_score": 0.85}


class TestReflectionResult:
    """Tests for ReflectionResult."""

    def test_creates_result(self) -> None:
        """Should create reflection result."""
        time_range = TimeRange.last_days(7)

        result = ReflectionResult(
            patterns=[],
            factors=[],
            anomalies=[],
            insights=[],
            time_range=time_range,
            episodes_analyzed=100,
            analysis_duration_seconds=2.5,
        )

        assert result.episodes_analyzed == 100
        assert result.analysis_duration_seconds == 2.5

    def test_to_dict(self) -> None:
        """Should convert to dictionary."""
        time_range = TimeRange.last_days(7)

        result = ReflectionResult(
            patterns=[
                Pattern(
                    id=uuid4(),
                    pattern_type=PatternType.RECURRING_SUCCESS,
                    name="Test",
                    description="Test",
                    confidence=0.8,
                    occurrence_count=5,
                    episode_ids=[],
                    first_seen=datetime.now(UTC),
                    last_seen=datetime.now(UTC),
                )
            ],
            factors=[],
            anomalies=[],
            insights=[],
            time_range=time_range,
            episodes_analyzed=50,
            analysis_duration_seconds=1.5,
        )

        data = result.to_dict()

        assert len(data["patterns"]) == 1
        assert data["episodes_analyzed"] == 50
        assert "time_range" in data
        assert "duration_hours" in data["time_range"]

    def test_summary(self) -> None:
        """Should generate summary text."""
        time_range = TimeRange.last_days(7)

        result = ReflectionResult(
            patterns=[
                Pattern(
                    id=uuid4(),
                    pattern_type=PatternType.RECURRING_SUCCESS,
                    name="Pattern 1",
                    description="Test",
                    confidence=0.8,
                    occurrence_count=5,
                    episode_ids=[],
                    first_seen=datetime.now(UTC),
                    last_seen=datetime.now(UTC),
                )
            ],
            factors=[
                Factor(
                    id=uuid4(),
                    factor_type=FactorType.ACTION,
                    name="Factor 1",
                    description="Test",
                    impact_score=0.6,
                    correlation=0.4,
                    sample_size=10,
                    positive_examples=[],
                    negative_examples=[],
                )
            ],
            anomalies=[],
            insights=[
                Insight(
                    id=uuid4(),
                    insight_type=InsightType.OPTIMIZATION,
                    title="Top Insight",
                    description="Test",
                    priority=0.9,
                    confidence=0.8,
                    source_patterns=[],
                    source_factors=[],
                    source_anomalies=[],
                    recommended_actions=["Action"],
                    generated_at=datetime.now(UTC),
                )
            ],
            time_range=time_range,
            episodes_analyzed=100,
            analysis_duration_seconds=2.0,
        )

        summary = result.summary

        assert "Reflection Analysis" in summary
        assert "Episodes analyzed: 100" in summary
        assert "Patterns detected: 1" in summary
        assert "Success/failure factors: 1" in summary
        assert "Insights generated: 1" in summary
        assert "Top insights:" in summary
        assert "Top Insight" in summary


class TestPatternType:
    """Tests for PatternType enum."""

    def test_all_pattern_types(self) -> None:
        """Should have all expected pattern types."""
        assert PatternType.RECURRING_SUCCESS.value == "recurring_success"
        assert PatternType.RECURRING_FAILURE.value == "recurring_failure"
        assert PatternType.ACTION_SEQUENCE.value == "action_sequence"
        assert PatternType.CONTEXT_CORRELATION.value == "context_correlation"
        assert PatternType.TEMPORAL.value == "temporal"
        assert PatternType.EFFICIENCY.value == "efficiency"


class TestFactorType:
    """Tests for FactorType enum."""

    def test_all_factor_types(self) -> None:
        """Should have all expected factor types."""
        assert FactorType.ACTION.value == "action"
        assert FactorType.CONTEXT.value == "context"
        assert FactorType.TIMING.value == "timing"
        assert FactorType.RESOURCE.value == "resource"
        assert FactorType.PRECEDING_STATE.value == "preceding_state"


class TestAnomalyType:
    """Tests for AnomalyType enum."""

    def test_all_anomaly_types(self) -> None:
        """Should have all expected anomaly types."""
        assert AnomalyType.UNUSUAL_DURATION.value == "unusual_duration"
        assert AnomalyType.UNEXPECTED_OUTCOME.value == "unexpected_outcome"
        assert AnomalyType.UNUSUAL_TOKEN_USAGE.value == "unusual_token_usage"
        assert AnomalyType.UNUSUAL_FAILURE_RATE.value == "unusual_failure_rate"
        assert AnomalyType.UNUSUAL_ACTION_PATTERN.value == "unusual_action_pattern"


class TestInsightType:
    """Tests for InsightType enum."""

    def test_all_insight_types(self) -> None:
        """Should have all expected insight types."""
        assert InsightType.OPTIMIZATION.value == "optimization"
        assert InsightType.WARNING.value == "warning"
        assert InsightType.LEARNING.value == "learning"
        assert InsightType.RECOMMENDATION.value == "recommendation"
        assert InsightType.TREND.value == "trend"
