"""
Test mean/median display configuration for average sensors.

This test verifies that:
1. Sensors with average values respect CONF_AVERAGE_SENSOR_DISPLAY setting
2. State shows the configured value (mean or median)
3. Attributes show the alternate value
4. Calculations that depend on averages use mean internally (not affected by display setting)
"""

from datetime import UTC, datetime, timedelta
import statistics
from unittest.mock import Mock

import pytest

from custom_components.tibber_prices.const import CONF_AVERAGE_SENSOR_DISPLAY, DEFAULT_AVERAGE_SENSOR_DISPLAY
from custom_components.tibber_prices.sensor.attributes.helpers import add_alternate_average_attribute
from custom_components.tibber_prices.utils.average import calculate_mean, calculate_median


@pytest.fixture
def mock_prices() -> list[dict]:
    """Create mock price data with known mean and median."""
    base_time = datetime(2025, 12, 18, 0, 0, tzinfo=UTC)
    # Prices: 10, 20, 30, 40, 100
    # Mean = 40.0, Median = 30.0 (intentionally different)
    return [
        {"startsAt": (base_time + timedelta(hours=i)).isoformat(), "total": price, "level": "normal"}
        for i, price in enumerate([10.0, 20.0, 30.0, 40.0, 100.0])
    ]


def test_average_price_today_displays_median_when_configured(
    mock_prices: list[dict],
) -> None:
    """Test that average_price_today sensor shows median in state when configured."""
    # Setup mock config entry with median display
    mock_entry = Mock()
    mock_entry.entry_id = "test_entry"
    mock_entry.options = {
        CONF_AVERAGE_SENSOR_DISPLAY: "median",
    }

    # Setup mock coordinator data with today's prices
    coordinator_data = {
        "priceInfo": mock_prices,
        "currency": "EUR",
    }

    # Mock coordinator
    mock_coordinator = Mock()
    mock_coordinator.data = coordinator_data
    mock_coordinator.config_entry = mock_entry
    mock_coordinator.time = Mock()
    mock_coordinator.time.now.return_value = datetime(2025, 12, 18, 12, 0, tzinfo=UTC)

    # Get prices for today
    prices = [float(p["total"]) for p in mock_prices]

    # Calculate expected values
    expected_mean = calculate_mean(prices)  # 40.0
    expected_median = calculate_median(prices)  # 30.0

    # Verify that mean and median are actually different (test setup)
    assert expected_mean != expected_median, "Test setup requires different mean and median"
    assert expected_mean == pytest.approx(40.0), f"Expected mean 40.0, got {expected_mean}"
    assert expected_median == pytest.approx(30.0), f"Expected median 30.0, got {expected_median}"


def test_average_price_today_displays_mean_when_configured(
    mock_prices: list[dict],
) -> None:
    """Test that average_price_today sensor shows mean in state when configured."""
    # Setup mock config entry with mean display
    mock_entry = Mock()
    mock_entry.entry_id = "test_entry"
    mock_entry.options = {
        CONF_AVERAGE_SENSOR_DISPLAY: "mean",
    }

    # Setup mock coordinator data
    coordinator_data = {
        "priceInfo": mock_prices,
        "currency": "EUR",
    }

    mock_coordinator = Mock()
    mock_coordinator.data = coordinator_data
    mock_coordinator.config_entry = mock_entry
    mock_coordinator.time = Mock()
    mock_coordinator.time.now.return_value = datetime(2025, 12, 18, 12, 0, tzinfo=UTC)

    # Get prices for today
    prices = [float(p["total"]) for p in mock_prices]

    # Calculate expected values
    expected_mean = calculate_mean(prices)  # 40.0
    expected_median = calculate_median(prices)  # 30.0

    # Verify setup
    assert expected_mean == pytest.approx(40.0)
    assert expected_median == pytest.approx(30.0)


def test_default_display_is_median() -> None:
    """Test that default display mode is median."""
    assert DEFAULT_AVERAGE_SENSOR_DISPLAY == "median", "Default should be median for consistency"


def test_rolling_hour_average_respects_display_setting() -> None:
    """Test that rolling hour average sensors respect display configuration."""
    # Create two config entries with different settings
    config_mean = Mock()
    config_mean.options = {CONF_AVERAGE_SENSOR_DISPLAY: "mean"}

    config_median = Mock()
    config_median.options = {CONF_AVERAGE_SENSOR_DISPLAY: "median"}

    # Test that the setting is read correctly
    assert config_mean.options.get(CONF_AVERAGE_SENSOR_DISPLAY) == "mean"
    assert config_median.options.get(CONF_AVERAGE_SENSOR_DISPLAY) == "median"


def test_calculations_always_use_mean_internally() -> None:
    """
    Test that internal calculations (like volatility) always use mean, not median.

    This verifies that CONF_AVERAGE_SENSOR_DISPLAY only affects STATE DISPLAY,
    not internal calculations that depend on averages.

    For example:
    - Volatility calculation uses mean (standard deviation / mean)
    - Price differences use mean
    - Trend detection uses mean

    The display setting should NOT affect these calculations.
    """
    # Sample data with different mean and median
    prices = [10.0, 20.0, 30.0, 40.0, 100.0]

    # Calculate mean
    mean = calculate_mean(prices)  # 40.0

    # Volatility calculation uses mean (coefficient of variation = std_dev / mean)
    # This should ALWAYS use mean, regardless of display setting
    assert mean == pytest.approx(40.0)

    # For volatility: std_dev / mean * 100
    # The mean here should be 40.0, not the median (30.0)

    std_dev = statistics.stdev(prices)
    coefficient_of_variation = (std_dev / mean) * 100

    # Verify calculation uses mean (40.0), not median (30.0)
    expected_cv_with_mean = (std_dev / 40.0) * 100
    expected_cv_with_median = (std_dev / 30.0) * 100

    assert coefficient_of_variation == pytest.approx(expected_cv_with_mean)
    assert coefficient_of_variation != pytest.approx(expected_cv_with_median), (
        "Volatility calculation should use mean, not median"
    )


def test_trend_calculation_uses_mean() -> None:
    """
    Test that trend calculations use mean for forward-looking averages.

    Trend detection compares:
    - Later half mean (next 2h, 3h, or 6h)
    - First half mean

    These should ALWAYS use arithmetic mean for accurate trend detection,
    regardless of display preference.
    """
    # Two sets of prices with different distributions
    first_half = [10.0, 20.0, 30.0]  # mean=20.0, median=20.0
    later_half = [40.0, 50.0, 100.0]  # mean=63.33, median=50.0

    # Calculate means (used in trend detection)
    first_mean = calculate_mean(first_half)
    later_mean = calculate_mean(later_half)

    # Trend percentage should use means
    trend_pct = ((later_mean - first_mean) / first_mean) * 100

    # Verify it uses mean (not median)
    assert first_mean == pytest.approx(20.0)
    assert later_mean == pytest.approx(63.33, rel=0.01)
    assert trend_pct > 200, "Trend should show >200% increase using means"

    # If we incorrectly used medians:

    first_median = statistics.median(first_half)  # 20.0
    later_median = statistics.median(later_half)  # 50.0
    wrong_trend_pct = ((later_median - first_median) / first_median) * 100

    assert wrong_trend_pct == pytest.approx(150.0)
    assert trend_pct != pytest.approx(wrong_trend_pct), "Trend calculation should use mean, not median"


def test_attribute_contains_alternate_value() -> None:
    """
    Test that attributes contain BOTH average values for automation consistency.

    Both price_mean and price_median should always be present in attributes,
    regardless of which value is displayed in state. The value matching the state
    will be excluded from recorder via dynamic _unrecorded_attributes.
    """
    # Mock config entry with median display
    mock_entry_median = Mock()
    mock_entry_median.options = {CONF_AVERAGE_SENSOR_DISPLAY: "median"}

    # Mock cached data
    cached_data = {
        "average_price_today_mean": 40.0,
        "average_price_today_median": 30.0,
    }

    # Test median display → BOTH mean AND median in attributes
    attributes_median_display = {}
    add_alternate_average_attribute(
        attributes_median_display,
        cached_data,
        "average_price_today",
        config_entry=mock_entry_median,
    )

    assert "price_mean" in attributes_median_display, "Both values should be in attributes"
    assert "price_median" in attributes_median_display, "Both values should be in attributes"
    assert attributes_median_display["price_mean"] == 40.0
    assert attributes_median_display["price_median"] == 30.0

    # Mock config entry with mean display
    mock_entry_mean = Mock()
    mock_entry_mean.options = {CONF_AVERAGE_SENSOR_DISPLAY: "mean"}

    # Test mean display → BOTH mean AND median in attributes
    attributes_mean_display = {}
    add_alternate_average_attribute(
        attributes_mean_display,
        cached_data,
        "average_price_today",
        config_entry=mock_entry_mean,
    )

    assert "price_median" in attributes_mean_display, "Both values should be in attributes"
    assert "price_mean" in attributes_mean_display, "Both values should be in attributes"
    assert attributes_mean_display["price_median"] == 30.0
    assert attributes_mean_display["price_mean"] == 40.0


def test_next_avg_sensors_respect_display_setting() -> None:
    """Test that next_avg_Nh sensors calculation returns both mean and median."""
    # Sample data with different mean and median
    prices = [10.0, 20.0, 30.0, 40.0, 100.0]

    # Calculate mean and median
    mean = calculate_mean(prices)
    median = calculate_median(prices)

    # Verify both values are calculated
    assert mean is not None, "Mean should be calculated"
    assert median is not None, "Median should be calculated"
    assert mean != median, "Test requires different mean and median"
    assert mean == pytest.approx(40.0), f"Expected mean 40.0, got {mean}"
    assert median == pytest.approx(30.0), f"Expected median 30.0, got {median}"


def test_24h_window_sensors_respect_display_setting() -> None:
    """Test that 24h trailing/leading average calculation returns both mean and median."""
    # Sample data with different mean and median
    prices = [10.0, 20.0, 30.0, 40.0, 100.0]

    # Calculate both statistics
    mean = calculate_mean(prices)
    median = calculate_median(prices)

    # Verify both are calculated
    assert mean is not None
    assert median is not None
    assert mean != median, "Test requires different mean and median"

    # The 24h window functions (calculate_trailing_24h_mean, calculate_leading_24h_mean)
    # return (mean, median) tuples, allowing sensor to choose which to display
