"""
Tests for Dynamic Business Rules Engine
"""

import pytest
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from app.ml.dynamic_rules_engine import DynamicRulesEngine


@pytest.fixture
def sample_sales_data():
    """Generate sample sales data for testing."""
    dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')

    # Base demand with day-of-week pattern
    base = 100
    quantities = []

    for date in dates:
        # Day of week pattern (weekends higher)
        dow_multiplier = 1.3 if date.dayofweek >= 5 else 1.0

        # Monthly seasonality (summer higher)
        month_multiplier = 1.2 if date.month in [6, 7, 8] else 1.0

        # Random noise
        noise = np.random.normal(1.0, 0.1)

        quantity = base * dow_multiplier * month_multiplier * noise
        quantities.append(quantity)

    return pd.DataFrame({
        'date': dates,
        'ds': dates,
        'quantity': quantities,
        'y': quantities
    })


@pytest.fixture
def sample_weather_data():
    """Generate sample weather data for testing."""
    dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')

    weather_conditions = []
    temperatures = []
    precipitation = []

    for date in dates:
        # Simulate weather patterns
        if np.random.random() < 0.1:  # 10% rainy days
            weather_conditions.append('rain')
            precipitation.append(np.random.uniform(5, 20))
        elif np.random.random() < 0.05:  # 5% snow
            weather_conditions.append('snow')
            precipitation.append(np.random.uniform(2, 10))
        else:
            weather_conditions.append('clear')
            precipitation.append(0)

        # Temperature varies by month
        base_temp = 10 + (date.month - 1) * 2
        temperatures.append(base_temp + np.random.normal(0, 5))

    return pd.DataFrame({
        'date': dates,
        'weather_condition': weather_conditions,
        'temperature': temperatures,
        'precipitation': precipitation
    })


@pytest.fixture
def sample_holiday_data():
    """Generate sample holiday data for testing."""
    dates = pd.date_range(start='2024-01-01', end='2024-12-31', freq='D')

    holidays = []

    # Add some holidays
    holiday_dates = {
        '2024-01-01': ('New Year', 'national'),
        '2024-03-29': ('Good Friday', 'religious'),
        '2024-04-01': ('Easter Monday', 'religious'),
        '2024-12-25': ('Christmas', 'religious'),
        '2024-12-26': ('Boxing Day', 'national')
    }

    for date in dates:
        date_str = date.strftime('%Y-%m-%d')
        if date_str in holiday_dates:
            name, htype = holiday_dates[date_str]
            holidays.append({
                'date': date,
                'is_holiday': True,
                'holiday_name': name,
                'holiday_type': htype
            })
        else:
            holidays.append({
                'date': date,
                'is_holiday': False,
                'holiday_name': None,
                'holiday_type': None
            })

    return pd.DataFrame(holidays)


@pytest.fixture
def sales_with_weather_impact(sample_sales_data, sample_weather_data):
    """Generate sales data with weather impact."""
    merged = sample_sales_data.merge(sample_weather_data, on='date')

    # Apply weather impact
    for idx, row in merged.iterrows():
        if row['weather_condition'] == 'rain':
            merged.at[idx, 'quantity'] *= 0.85  # -15% for rain
            merged.at[idx, 'y'] *= 0.85
        elif row['weather_condition'] == 'snow':
            merged.at[idx, 'quantity'] *= 0.75  # -25% for snow
            merged.at[idx, 'y'] *= 0.75

    return merged


@pytest.fixture
def sales_with_holiday_impact(sample_sales_data, sample_holiday_data):
    """Generate sales data with holiday impact."""
    merged = sample_sales_data.merge(sample_holiday_data, on='date')

    # Apply holiday impact
    for idx, row in merged.iterrows():
        if row['is_holiday'] and row['holiday_type'] == 'religious':
            merged.at[idx, 'quantity'] *= 1.6  # +60% for religious holidays
            merged.at[idx, 'y'] *= 1.6
        elif row['is_holiday']:
            merged.at[idx, 'quantity'] *= 1.3  # +30% for national holidays
            merged.at[idx, 'y'] *= 1.3

    return merged


@pytest.mark.asyncio
async def test_learn_weather_rules(sales_with_weather_impact, sample_weather_data):
    """Test weather rules learning."""
    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=sales_with_weather_impact,
        external_data=sample_weather_data,
        min_samples=5
    )

    # Check weather rules were learned
    assert 'weather' in results['rules']
    assert 'baseline_avg' in results['rules']['weather']
    assert 'conditions' in results['rules']['weather']

    # Check rain rule learned
    if 'rain' in results['rules']['weather']['conditions']:
        rain_rule = results['rules']['weather']['conditions']['rain']
        assert 'learned_multiplier' in rain_rule
        assert 'learned_impact_pct' in rain_rule
        assert rain_rule['sample_size'] >= 5

        # Learned multiplier should be close to 0.85 (we applied -15% impact)
        assert 0.75 < rain_rule['learned_multiplier'] < 0.95

    # Check insights generated
    assert 'insights' in results
    assert len(results['insights']) > 0


@pytest.mark.asyncio
async def test_learn_holiday_rules(sales_with_holiday_impact, sample_holiday_data):
    """Test holiday rules learning."""
    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=sales_with_holiday_impact,
        external_data=sample_holiday_data,
        min_samples=2
    )

    # Check holiday rules were learned
    assert 'holidays' in results['rules']
    assert 'baseline_avg' in results['rules']['holidays']

    if 'holiday_types' in results['rules']['holidays']:
        holiday_types = results['rules']['holidays']['holiday_types']

        # Check religious holidays learned higher impact than national
        if 'religious' in holiday_types and 'national' in holiday_types:
            religious_mult = holiday_types['religious']['learned_multiplier']
            national_mult = holiday_types['national']['learned_multiplier']

            # Religious should have higher multiplier (we applied 1.6 vs 1.3)
            assert religious_mult > national_mult


@pytest.mark.asyncio
async def test_learn_day_of_week_rules(sample_sales_data):
    """Test day-of-week pattern learning."""
    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=sample_sales_data,
        external_data=None,
        min_samples=10
    )

    # Check day-of-week rules learned
    assert 'day_of_week' in results['rules']
    assert 'days' in results['rules']['day_of_week']

    days = results['rules']['day_of_week']['days']

    # Weekend should have higher multipliers (we applied 1.3x)
    if 'Saturday' in days and 'Monday' in days:
        saturday_mult = days['Saturday']['learned_multiplier']
        monday_mult = days['Monday']['learned_multiplier']

        assert saturday_mult > monday_mult


@pytest.mark.asyncio
async def test_learn_month_rules(sample_sales_data):
    """Test monthly seasonality learning."""
    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=sample_sales_data,
        external_data=None,
        min_samples=10
    )

    # Check month rules learned
    assert 'months' in results['rules']
    assert 'months' in results['rules']['months']

    months = results['rules']['months']['months']

    # Summer months (June, July, August) should have higher multipliers
    if 'July' in months and 'January' in months:
        july_mult = months['July']['learned_multiplier']
        january_mult = months['January']['learned_multiplier']

        assert july_mult > january_mult


@pytest.mark.asyncio
async def test_insight_generation_weather_mismatch(sales_with_weather_impact, sample_weather_data):
    """Test that insights are generated when learned rules differ from hardcoded."""
    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=sales_with_weather_impact,
        external_data=sample_weather_data,
        min_samples=5
    )

    # Should generate insights comparing learned vs hardcoded
    insights = results['insights']

    # Check for weather-related insights
    weather_insights = [i for i in insights if 'weather' in i.get('title', '').lower()]

    if weather_insights:
        insight = weather_insights[0]
        assert 'type' in insight
        assert 'priority' in insight
        assert 'confidence' in insight
        assert 'metrics_json' in insight
        assert 'actionable' in insight
        assert 'recommendation_actions' in insight


@pytest.mark.asyncio
async def test_confidence_calculation():
    """Test confidence score calculation."""
    engine = DynamicRulesEngine()

    # High confidence: large sample, low p-value
    high_conf = engine._calculate_confidence(sample_size=150, p_value=0.001)
    assert high_conf >= 90

    # Medium confidence: moderate sample, moderate p-value
    med_conf = engine._calculate_confidence(sample_size=50, p_value=0.03)
    assert 60 <= med_conf < 90

    # Low confidence: small sample, high p-value
    low_conf = engine._calculate_confidence(sample_size=15, p_value=0.12)
    assert low_conf < 60


def test_get_rule():
    """Test getting learned rules."""
    engine = DynamicRulesEngine()

    # Manually set some rules for testing
    engine.weather_rules['product-1'] = {
        'conditions': {
            'rain': {
                'learned_multiplier': 0.85
            }
        }
    }

    engine.dow_rules['product-1'] = {
        'days': {
            'Saturday': {
                'learned_multiplier': 1.25
            }
        }
    }

    # Test retrieval
    rain_mult = engine.get_rule('product-1', 'weather', 'rain')
    assert rain_mult == 0.85

    saturday_mult = engine.get_rule('product-1', 'day_of_week', 'Saturday')
    assert saturday_mult == 1.25

    # Test non-existent rule
    unknown = engine.get_rule('product-1', 'weather', 'tornado')
    assert unknown is None


def test_export_rules_for_prophet():
    """Test exporting rules for Prophet integration."""
    engine = DynamicRulesEngine()

    # Set up some test rules
    engine.weather_rules['product-1'] = {'conditions': {'rain': {'learned_multiplier': 0.85}}}
    engine.holiday_rules['product-1'] = {'holiday_types': {'Christmas': {'learned_multiplier': 1.7}}}

    # Export
    exported = engine.export_rules_for_prophet('product-1')

    assert 'weather' in exported
    assert 'holidays' in exported
    assert 'events' in exported
    assert 'day_of_week' in exported
    assert 'months' in exported


@pytest.mark.asyncio
async def test_no_external_data(sample_sales_data):
    """Test that engine works with sales data only (no external data)."""
    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=sample_sales_data,
        external_data=None,
        min_samples=10
    )

    # Should still learn DOW and month patterns
    assert 'day_of_week' in results['rules']
    assert 'months' in results['rules']

    # Weather/holiday/event rules should not be present
    assert 'weather' not in results['rules'] or len(results['rules']['weather'].get('conditions', {})) == 0


@pytest.mark.asyncio
async def test_insufficient_samples(sample_sales_data):
    """Test handling of insufficient sample sizes."""
    # Use only 30 days of data
    small_data = sample_sales_data.head(30)

    engine = DynamicRulesEngine()

    results = await engine.learn_all_rules(
        tenant_id='test-tenant',
        inventory_product_id='test-product',
        sales_data=small_data,
        external_data=None,
        min_samples=50  # Require more samples than available
    )

    # Should still return results but with fewer learned rules
    assert 'rules' in results
    assert 'insights' in results
