"""
Tests for Energy Calculator Module.

This module tests the energy score calculation functionality including:
- Multi-source weighted calculation
- Temporal weighting
- Degraded mode (when sources are unavailable)
- Score normalization (0-100)
- Confidence calculation
"""

import pytest
from datetime import datetime, timedelta
from app.ml.energy_calculator import (
    calculate_energy_score,
    apply_temporal_weighting,
    calculate_confidence,
    normalize_score,
    apply_source_weights,
    adjust_weights_for_degraded_mode
)


class TestCalculateEnergyScore:
    """Test the main energy score calculation function."""

    def test_calculate_energy_score_with_all_sources(self):
        """Test energy calculation with all three sources available."""
        # Arrange
        match_id = 1
        team_id = 1
        twitter_sentiments = [
            {'compound': 0.5, 'positive': 0.6, 'negative': 0.2, 'neutral': 0.2, 'sentiment': 'positive'}
        ]
        reddit_sentiments = [
            {'compound': 0.3, 'positive': 0.4, 'negative': 0.3, 'neutral': 0.3, 'sentiment': 'positive'}
        ]
        rss_sentiments = [
            {'compound': 0.4, 'positive': 0.5, 'negative': 0.2, 'neutral': 0.3, 'sentiment': 'positive'}
        ]

        # Act
        result = calculate_energy_score(
            match_id=match_id,
            team_id=team_id,
            twitter_sentiments=twitter_sentiments,
            reddit_sentiments=reddit_sentiments,
            rss_sentiments=rss_sentiments
        )

        # Assert
        assert 'score' in result
        assert 'confidence' in result
        assert 'sources_used' in result
        assert 0 <= result['score'] <= 100
        assert len(result['sources_used']) == 3
        assert result['confidence'] > 0.6  # High confidence with all sources

    def test_calculate_energy_score_with_twitter_only(self):
        """Test energy calculation with only Twitter source available (degraded mode)."""
        # Arrange
        match_id = 1
        team_id = 1
        twitter_sentiments = [
            {'compound': 0.5, 'positive': 0.6, 'negative': 0.2, 'neutral': 0.2, 'sentiment': 'positive'}
        ]
        reddit_sentiments = []
        rss_sentiments = []

        # Act
        result = calculate_energy_score(
            match_id=match_id,
            team_id=team_id,
            twitter_sentiments=twitter_sentiments,
            reddit_sentiments=reddit_sentiments,
            rss_sentiments=rss_sentiments
        )

        # Assert
        assert 'score' in result
        assert 'confidence' in result
        assert 'sources_used' in result
        assert 0 <= result['score'] <= 100
        assert len(result['sources_used']) == 1
        assert 'twitter' in result['sources_used']
        assert result['confidence'] < 0.6  # Lower confidence in degraded mode

    def test_calculate_energy_score_no_sentiment_data(self):
        """Test energy calculation with no sentiment data."""
        # Arrange
        match_id = 1
        team_id = 1

        # Act
        result = calculate_energy_score(
            match_id=match_id,
            team_id=team_id,
            twitter_sentiments=[],
            reddit_sentiments=[],
            rss_sentiments=[]
        )

        # Assert
        assert result['score'] == 0.0
        assert result['confidence'] == 0.0
        assert len(result['sources_used']) == 0


class TestApplySourceWeights:
    """Test source weight application and adjustment."""

    def test_apply_source_weights_all_sources(self):
        """Test applying weights with all sources available."""
        # Arrange
        twitter_score = 50.0
        reddit_score = 40.0
        rss_score = 30.0
        available_sources = ['twitter', 'reddit', 'rss']

        # Act
        weighted_score = apply_source_weights(
            twitter_score=twitter_score,
            reddit_score=reddit_score,
            rss_score=rss_score,
            available_sources=available_sources
        )

        # Assert
        expected = (50.0 * 0.60) + (40.0 * 0.25) + (30.0 * 0.15)
        assert weighted_score == expected

    def test_apply_source_weights_twitter_only(self):
        """Test applying weights with only Twitter available."""
        # Arrange
        twitter_score = 50.0
        reddit_score = 0.0
        rss_score = 0.0
        available_sources = ['twitter']

        # Act
        weighted_score = apply_source_weights(
            twitter_score=twitter_score,
            reddit_score=reddit_score,
            rss_score=rss_score,
            available_sources=available_sources
        )

        # Assert
        assert weighted_score == 50.0  # 100% of weight goes to Twitter

    def test_adjust_weights_for_degraded_mode(self):
        """Test weight adjustment in degraded mode."""
        # Arrange
        original_weights = {
            'twitter': 0.60,
            'reddit': 0.25,
            'rss': 0.15
        }
        available_sources = ['twitter', 'reddit']

        # Act
        adjusted_weights = adjust_weights_for_degraded_mode(
            original_weights=original_weights,
            available_sources=available_sources
        )

        # Assert
        assert len(adjusted_weights) == 2
        assert 'twitter' in adjusted_weights
        assert 'reddit' in adjusted_weights
        assert 'rss' not in adjusted_weights
        # Total should be 1.0
        total_weight = sum(adjusted_weights.values())
        assert abs(total_weight - 1.0) < 0.001


class TestApplyTemporalWeighting:
    """Test temporal weighting of sentiment scores."""

    def test_temporal_weighting_recent_tweets(self):
        """Test temporal weighting with recent tweets (within 1 hour)."""
        # Arrange
        base_score = 50.0
        now = datetime.utcnow()
        tweets_with_timestamps = [
            {
                'compound': 0.5,
                'created_at': now - timedelta(minutes=30)  # 30 minutes ago
            },
            {
                'compound': 0.6,
                'created_at': now - timedelta(minutes=15)  # 15 minutes ago
            }
        ]

        # Act
        weighted_score = apply_temporal_weighting(
            base_score=base_score,
            tweets_with_timestamps=tweets_with_timestamps
        )

        # Assert
        # Recent tweets should have higher weight (close to 1.0)
        # Score should be close to original but weighted by recency
        assert 0 <= weighted_score <= 100

    def test_temporal_weighting_old_tweets(self):
        """Test temporal weighting with old tweets (24+ hours)."""
        # Arrange
        base_score = 50.0
        now = datetime.utcnow()
        tweets_with_timestamps = [
            {
                'compound': 0.5,
                'created_at': now - timedelta(hours=30)  # 30 hours ago
            },
            {
                'compound': 0.6,
                'created_at': now - timedelta(hours=25)  # 25 hours ago
            }
        ]

        # Act
        weighted_score = apply_temporal_weighting(
            base_score=base_score,
            tweets_with_timestamps=tweets_with_timestamps
        )

        # Assert
        # Old tweets should have lower weight (around 0.5)
        assert 0 <= weighted_score <= 100


class TestNormalizeScore:
    """Test score normalization to 0-100 range."""

    def test_normalize_score_negative(self):
        """Test normalization of negative scores."""
        # Act
        normalized = normalize_score(-1.5)

        # Assert
        assert normalized == 0.0

    def test_normalize_score_positive(self):
        """Test normalization of positive scores."""
        # Act
        normalized = normalize_score(150.0)

        # Assert
        assert normalized == 100.0

    def test_normalize_score_in_range(self):
        """Test normalization of scores within range."""
        # Arrange & Act
        normalized_0 = normalize_score(0.0)
        normalized_50 = normalize_score(50.0)
        normalized_100 = normalize_score(100.0)

        # Assert
        assert normalized_0 == 0.0
        assert normalized_50 == 50.0
        assert normalized_100 == 100.0


class TestCalculateConfidence:
    """Test confidence level calculation."""

    def test_confidence_all_sources(self):
        """Test confidence with all sources available."""
        # Arrange
        available_sources = ['twitter', 'reddit', 'rss']
        total_weight = 1.0

        # Act
        confidence = calculate_confidence(
            available_sources=available_sources,
            total_weight=total_weight
        )

        # Assert
        assert confidence > 0.6  # High confidence
        assert confidence <= 1.0

    def test_confidence_single_source(self):
        """Test confidence with only one source available."""
        # Arrange
        available_sources = ['twitter']
        total_weight = 0.6

        # Act
        confidence = calculate_confidence(
            available_sources=available_sources,
            total_weight=total_weight
        )

        # Assert
        assert confidence < 0.6  # Lower confidence
        assert confidence >= 0.3

    def test_confidence_no_sources(self):
        """Test confidence with no sources available."""
        # Arrange
        available_sources = []
        total_weight = 0.0

        # Act
        confidence = calculate_confidence(
            available_sources=available_sources,
            total_weight=total_weight
        )

        # Assert
        assert confidence == 0.0


class TestEnergyFormula:
    """Test the core energy formula: Score = (Positive - Negative) × Volume × Virality."""

    def test_formula_positive_sentiment(self):
        """Test energy formula with positive sentiment."""
        # Arrange
        positive_ratio = 0.7
        negative_ratio = 0.2
        volume = 100
        virality = 1.5

        # Act
        score = (positive_ratio - negative_ratio) * volume * virality

        # Assert
        assert score > 0  # Positive energy

    def test_formula_negative_sentiment(self):
        """Test energy formula with negative sentiment."""
        # Arrange
        positive_ratio = 0.2
        negative_ratio = 0.7
        volume = 100
        virality = 1.5

        # Act
        score = (positive_ratio - negative_ratio) * volume * virality

        # Assert
        assert score < 0  # Negative energy

    def test_formula_neutral_sentiment(self):
        """Test energy formula with neutral sentiment."""
        # Arrange
        positive_ratio = 0.5
        negative_ratio = 0.5
        volume = 100
        virality = 1.5

        # Act
        score = (positive_ratio - negative_ratio) * volume * virality

        # Assert
        assert score == 0.0  # Neutral energy

    def test_formula_zero_volume(self):
        """Test energy formula with zero volume."""
        # Arrange
        positive_ratio = 0.7
        negative_ratio = 0.2
        volume = 0
        virality = 1.5

        # Act
        score = (positive_ratio - negative_ratio) * volume * virality

        # Assert
        assert score == 0.0  # No volume means no energy


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