"""
Tests for Importance Scoring System (M.1)

Tests: frequency tracking, emotion analysis, impact evaluation, and composite importance scoring.
"""

import pytest
from datetime import UTC, datetime, timedelta
from unittest.mock import MagicMock

from app.services.memory.frequency_tracker import FrequencyTracker
from app.services.memory.emotion_analyzer import EmotionAnalyzer
from app.services.memory.impact_evaluator import ImpactEvaluator
from app.services.memory.importance_scorer import ImportanceScorer, ImportanceLevel


def create_mock_memory(
    frequency_count: int = 0,
    last_recalled_at=None,
    content: str = "",
    emotion_tags: list = None,
    associated_topics: list = None,
    last_accessed_at=None,
):
    """Create a mock UserMemory for testing"""
    memory = MagicMock()
    memory.frequency_count = frequency_count
    memory.last_recalled_at = last_recalled_at
    memory.content = content
    memory.emotion_tags = emotion_tags or []
    memory.associated_topics = associated_topics or []
    memory.last_accessed_at = last_accessed_at
    memory.importance_score = 0.5
    memory.importance_level = "medium"
    return memory


class TestFrequencyTracker:
    """Test frequency tracking"""

    def test_increment(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(frequency_count=5)

        result = tracker.increment(memory)

        assert result.frequency_count == 6
        assert result.last_recalled_at is not None

    def test_increment_from_zero(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(frequency_count=0)

        result = tracker.increment(memory)

        assert result.frequency_count == 1

    def test_get_frequency_score_zero(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(frequency_count=0)

        score = tracker.get_frequency_score(memory)

        assert score == 0.0

    def test_get_frequency_score_normal(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(frequency_count=5)

        score = tracker.get_frequency_score(memory)

        # log(1+5) / log(1+10) ≈ log(6)/log(11) ≈ 0.778 / 1.041 ≈ 0.747
        assert 0.7 < score < 0.8

    def test_get_frequency_score_capped(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(frequency_count=100)

        score = tracker.get_frequency_score(memory)

        # Should be capped at 1.0
        assert score <= 1.0

    def test_get_recency_score_recent(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(last_recalled_at=datetime.now(UTC))

        score = tracker.get_recency_score(memory)

        assert score > 0.9

    def test_get_recency_score_old(self):
        tracker = FrequencyTracker()
        old_date = datetime.now(UTC) - timedelta(days=60)
        memory = create_mock_memory(last_recalled_at=old_date)

        score = tracker.get_recency_score(memory)

        # ~60 days old with 30-day half-life should be ~0.25
        assert score < 0.3

    def test_get_recency_score_never_recalled(self):
        tracker = FrequencyTracker()
        memory = create_mock_memory(last_recalled_at=None)

        score = tracker.get_recency_score(memory)

        assert score == 0.0

    def test_get_time_decay(self):
        tracker = FrequencyTracker()
        recent = datetime.now(UTC) - timedelta(days=7)
        memory = create_mock_memory(last_accessed_at=recent)

        decay = tracker.get_time_decay(memory)

        # ~7 days with 30-day half-life: exp(-7/30) ≈ 0.79
        assert 0.7 < decay < 0.9


class TestEmotionAnalyzer:
    """Test emotion analysis"""

    def test_extract_high_intensity(self):
        analyzer = EmotionAnalyzer()
        text = "这件事很重要，我急需解决！"

        matched = analyzer.extract(text)

        assert "很重要" in matched or "急" in matched

    def test_extract_worry(self):
        analyzer = EmotionAnalyzer()
        text = "我很担心这个问题"

        matched = analyzer.extract(text)

        assert "担心" in matched

    def test_extract_no_emotion(self):
        analyzer = EmotionAnalyzer()
        text = "今天天气不错"

        matched = analyzer.extract(text)

        assert len(matched) == 0

    def test_extract_urgency_pattern(self):
        analyzer = EmotionAnalyzer()
        text = "马上要迟到了"

        matched = analyzer.extract(text)

        assert any("URGENCY" in m for m in matched)

    def test_calculate_score_high(self):
        analyzer = EmotionAnalyzer()
        memory = create_mock_memory(content="这件事非常重要，急需解决！")

        score = analyzer.calculate_score(memory)

        assert score >= 0.9

    def test_calculate_score_neutral(self):
        analyzer = EmotionAnalyzer()
        memory = create_mock_memory(content="今天吃了苹果")

        score = analyzer.calculate_score(memory)

        assert score == 0.0

    def test_calculate_score_from_emotion_tags(self):
        analyzer = EmotionAnalyzer()
        memory = create_mock_memory(emotion_tags=["急", "很重要"])

        score = analyzer.calculate_score(memory)

        assert score >= 0.9

    def test_get_emotion_profile(self):
        analyzer = EmotionAnalyzer()
        text = "我很担心这个问题，必须马上解决"

        profile = analyzer.get_emotion_profile(text)

        assert "matched_keywords" in profile
        assert "max_weight" in profile
        assert profile["max_weight"] > 0


class TestImpactEvaluator:
    """Test impact evaluation"""

    def test_evaluate_no_topics(self):
        evaluator = ImpactEvaluator()
        memory = create_mock_memory(associated_topics=[])

        score = evaluator.evaluate(memory)

        assert score == 0.0

    def test_evaluate_single_topic(self):
        evaluator = ImpactEvaluator()
        memory = create_mock_memory(associated_topics=["工作"])

        score = evaluator.evaluate(memory)

        # 1 topic / 5 threshold = 0.2
        assert score == 0.2

    def test_evaluate_full_topics(self):
        evaluator = ImpactEvaluator()
        memory = create_mock_memory(associated_topics=["工作", "健康", "家庭", "财务", "爱好"])

        score = evaluator.evaluate(memory)

        # 5 topics / 5 threshold = 1.0
        assert score == 1.0

    def test_evaluate_over_threshold(self):
        evaluator = ImpactEvaluator()
        memory = create_mock_memory(associated_topics=["a", "b", "c", "d", "e", "f", "g"])

        score = evaluator.evaluate(memory)

        # Capped at 1.0
        assert score == 1.0

    def test_get_topic_overlap(self):
        evaluator = ImpactEvaluator()
        memory_a = create_mock_memory(associated_topics=["工作", "健康", "家庭"])
        memory_b = create_mock_memory(associated_topics=["工作", "健康", "爱好"])

        overlap = evaluator.get_topic_overlap(memory_a, memory_b)

        # Intersection: {工作, 健康} = 2, Union: {工作, 健康, 家庭, 爱好} = 4
        # 2/4 = 0.5
        assert overlap == 0.5

    def test_rank_by_impact(self):
        evaluator = ImpactEvaluator()
        memory_low = create_mock_memory(associated_topics=["a"])
        memory_high = create_mock_memory(associated_topics=["a", "b", "c", "d", "e"])

        ranked = evaluator.rank_by_impact([memory_low, memory_high])

        assert ranked[0] == memory_high
        assert ranked[1] == memory_low


class TestImportanceScorer:
    """Test composite importance scoring"""

    def test_calculate_score_fresh_memory(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=0,
            last_recalled_at=None,
            content="今天吃了苹果",
            associated_topics=[],
        )

        score = scorer.calculate_score(memory)

        # All zeros, should be ~0
        assert score < 0.1

    def test_calculate_score_high_frequency(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=10,
            last_recalled_at=datetime.now(UTC),
            content="工作相关",
            associated_topics=["工作"],
        )

        score = scorer.calculate_score(memory)

        # High frequency (log(11)/log(11) ≈ 1.0) * 0.35 + recency * 0.20 + emotion * 0.25 + impact * 0.20
        # ≈ 0.35 + 0.20 + 0 + 0.04 = 0.59
        assert score > 0.5

    def test_calculate_score_with_emotion(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=1,
            content="这件事很重要，我急需解决！",
            associated_topics=["工作"],
        )

        score = scorer.calculate_score(memory)

        # Emotion score ~0.9 * 0.25 = 0.225
        assert score > 0.2

    def test_calculate_score_high_all_factors(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=10,
            last_recalled_at=datetime.now(UTC),
            content="这个问题非常紧急，必须马上处理！",
            associated_topics=["工作", "健康", "家庭", "财务", "爱好"],
        )

        score = scorer.calculate_score(memory)

        # All factors high
        assert score > 0.7

    def test_get_importance_level_high(self):
        scorer = ImportanceScorer()

        level = scorer.get_importance_level(0.85)

        assert level == ImportanceLevel.HIGH

    def test_get_importance_level_medium(self):
        scorer = ImportanceScorer()

        level = scorer.get_importance_level(0.6)

        assert level == ImportanceLevel.MEDIUM

    def test_get_importance_level_low(self):
        scorer = ImportanceScorer()

        level = scorer.get_importance_level(0.3)

        assert level == ImportanceLevel.LOW

    def test_should_escalate_by_score(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=10,
            last_recalled_at=datetime.now(UTC),
            content="紧急！非常重要！",
            associated_topics=["a", "b", "c", "d", "e"],
        )

        result = scorer.should_escalate(memory)

        # With high freq (0.35) + recent (0.20) + emotion (0.25) + many topics (0.20) = 1.0
        assert result is True

    def test_should_escalate_by_emotion(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=1,
            content="紧急！非常重要！",
            associated_topics=[],
        )

        result = scorer.should_escalate(memory)

        # Emotion intensity alone triggers escalation
        assert result is True

    def test_should_not_escalate(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=0,
            content="今天天气不错",
            associated_topics=[],
        )

        result = scorer.should_escalate(memory)

        assert result is False

    def test_score_and_classify(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(frequency_count=10, associated_topics=["a", "b", "c", "d", "e"])

        score, level = scorer.score_and_classify(memory)

        assert isinstance(score, float)
        assert 0.0 <= score <= 1.0
        assert level in ImportanceLevel

    def test_update_memory_importance(self):
        scorer = ImportanceScorer()
        memory = create_mock_memory(
            frequency_count=10,
            last_recalled_at=datetime.now(UTC),
            content="这个问题非常重要！",
            associated_topics=["工作", "健康"],
        )

        result = scorer.update_memory_importance(memory)

        assert result.importance_score is not None
        assert result.importance_level in ["high", "medium", "low"]


class TestImportanceLevel:
    """Test ImportanceLevel enum"""

    def test_level_values(self):
        assert ImportanceLevel.HIGH.value == "high"
        assert ImportanceLevel.MEDIUM.value == "medium"
        assert ImportanceLevel.LOW.value == "low"

    def test_is_string_enum(self):
        assert isinstance(ImportanceLevel.HIGH, str)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
