import unittest
from unittest.mock import MagicMock, patch
from datetime import date, timedelta
import pandas as pd
from health.analytics.engine import HealthAnalyst

class TestHealthAnalyst(unittest.TestCase):
    def setUp(self):
        self.mock_query = MagicMock()
        self.mock_storage = MagicMock()
        
        # Patch classes BEFORE instantiating HealthAnalyst
        with patch("health.analytics.engine.HealthDataQuery", return_value=self.mock_query), \
             patch("health.analytics.engine.ManualLogStorage", return_value=self.mock_storage):
            self.analyst = HealthAnalyst()
            # Re-attach mocks to the instance (because __init__ created new mocks during the patch)
            self.analyst.query = self.mock_query
            self.analyst.manual_storage = self.mock_storage

    def test_recovery_correlation_logic(self):
        """Test that alcohol correlation is calculated correctly."""
        # Setup 5 days of data
        # Days 1, 3: Alcohol -> Expect low sleep on Day 2, 4
        # Days 2, 4, 5: Sober -> Expect high sleep on Day 3, 5, 6
        
        # Start date: Jan 1. End date: Jan 5.
        start_date = date(2024, 1, 1)
        
        # Mock Metric Range (Sleep)
        # We need data for Jan 1 to Jan 6 (since we look ahead)
        sleep_data = [
            {"date": "2024-01-01", "overall_sleep_score": 80},
            {"date": "2024-01-02", "overall_sleep_score": 50}, # After alc
            {"date": "2024-01-03", "overall_sleep_score": 85},
            {"date": "2024-01-04", "overall_sleep_score": 55}, # After alc
            {"date": "2024-01-05", "overall_sleep_score": 90},
            {"date": "2024-01-06", "overall_sleep_score": 90},
        ]
        
        # Mock metrics return - simplistic: return same list for any metric call
        # In reality get_dataframe calls get_metric_range for each metric
        self.mock_query.get_metric_range.side_effect = lambda m, s, e: sleep_data if m == 'sleep' else []

        # Mock Manual Logs
        # Alcohol on Jan 1 and Jan 3
        log1 = MagicMock()
        log1.log_date = "2024-01-01"
        log1.alcohol_entries = ["wine"]
        log1.fasting_mode = None
        log1.supplement_entries = []
        
        log2 = MagicMock()
        log2.log_date = "2024-01-03"
        log2.alcohol_entries = ["beer"]
        log2.fasting_mode = None
        log2.supplement_entries = []
        
        self.mock_storage.get_logs_in_range.return_value = [log1, log2]
        
        # Run Analysis
        # Note: We need to mock datetime.date.today logic inside the class or pass dates?
        # The class uses date.today() - timedelta(days). 
        # To make this test deterministic, we should ideally refactor HealthAnalyst to accept end_date,
        # but for now we can rely on the fact that get_dataframe builds relative to today.
        # BUT: our mock data is hardcoded to Jan 2024. 
        # FIX: We will mock get_dataframe directly to bypass date logic and test logic only, 
        # OR we just test get_dataframe construction.
        
        # Let's test the logic on a manually constructed DF to ensure the math is right.
        df = pd.DataFrame([
            {"date": "2024-01-01", "has_alcohol": 1, "sleep": 80, "rhr": 60, "hrv": 50},
            {"date": "2024-01-02", "has_alcohol": 0, "sleep": 50, "rhr": 65, "hrv": 40},
            {"date": "2024-01-03", "has_alcohol": 1, "sleep": 85, "rhr": 55, "hrv": 60},
            {"date": "2024-01-04", "has_alcohol": 0, "sleep": 55, "rhr": 62, "hrv": 45},
        ])
        
        # Mock get_dataframe to return this
        with patch.object(self.analyst, 'get_dataframe', return_value=df):
            stats = self.analyst.analyze_recovery_correlations(days=4)
            
            # Alcohol on Jan 1 -> Sleep Jan 2 (50)
            # Alcohol on Jan 3 -> Sleep Jan 4 (55)
            # Avg Alcohol Next Sleep = 52.5
            
            # No alcohol on Jan 2 -> Sleep Jan 3 (85)
            # No alcohol on Jan 4 -> Next sleep is NaN (since no Jan 5 data row)
            # So Avg Sober Next Sleep = 85
            
            # Sleep Diff = 52.5 - 85 = -32.5
            
            self.assertIn('alcohol_impact', stats)
            self.assertAlmostEqual(stats['alcohol_impact']['avg_sleep_alcohol'], 52.5)
            self.assertAlmostEqual(stats['alcohol_impact']['avg_sleep_sober'], 85.0)

    def test_fitness_trend_logic(self):
        """Test RHR trend detection."""
        df = pd.DataFrame({
            "date": pd.date_range(start="2024-01-01", periods=20),
            "rhr": [60]*5 + [58]*5 + [56]*5 + [55]*5, # Decreasing RHR
            "steps": [10000]*20
        })
        
        with patch.object(self.analyst, 'get_dataframe', return_value=df):
            stats = self.analyst.analyze_fitness_trends(days=20)
            self.assertEqual(stats['trend'], "improving")
            # Start RHR ~60, End RHR ~55
            self.assertTrue(stats['end_rhr_7d_avg'] < stats['start_rhr_7d_avg'])

if __name__ == '__main__':
    unittest.main()
