"""
Recovery Metrics Calculation Engine

Implements improved Recovery Score (S1 from visualization concept):
- HRV vs. baseline
- RHR vs. baseline
- Sleep duration vs. target
- Sleep debt calculation
- Sleep regularity
- Recent load balance
- Data quality assessment

All metrics designed for robust scoring.
"""
from datetime import datetime, timedelta
from typing import Optional, Dict
import statistics

from db import get_db, get_cursor


# ============================================================================
# Recovery Score v2 (Improved from v9d)
# ============================================================================

def calculate_recovery_score_v2(profile_id: str) -> Optional[int]:
    """
    Improved recovery/readiness score (0-100)

    Components:
    - HRV status (25%)
    - RHR status (20%)
    - Sleep duration (20%)
    - Sleep debt (10%)
    - Sleep regularity (10%)
    - Recent load balance (10%)
    - Data quality (5%)
    """
    components = []

    # 1. HRV status (25%)
    hrv_score = _score_hrv_vs_baseline(profile_id)
    if hrv_score is not None:
        components.append(('hrv', hrv_score, 25))

    # 2. RHR status (20%)
    rhr_score = _score_rhr_vs_baseline(profile_id)
    if rhr_score is not None:
        components.append(('rhr', rhr_score, 20))

    # 3. Sleep duration (20%)
    sleep_duration_score = _score_sleep_duration(profile_id)
    if sleep_duration_score is not None:
        components.append(('sleep_duration', sleep_duration_score, 20))

    # 4. Sleep debt (10%)
    sleep_debt_score = _score_sleep_debt(profile_id)
    if sleep_debt_score is not None:
        components.append(('sleep_debt', sleep_debt_score, 10))

    # 5. Sleep regularity (10%)
    regularity_score = _score_sleep_regularity(profile_id)
    if regularity_score is not None:
        components.append(('regularity', regularity_score, 10))

    # 6. Recent load balance (10%)
    load_score = _score_recent_load_balance(profile_id)
    if load_score is not None:
        components.append(('load', load_score, 10))

    # 7. Data quality (5%)
    quality_score = _score_recovery_data_quality(profile_id)
    if quality_score is not None:
        components.append(('data_quality', quality_score, 5))

    if not components:
        return None

    # Weighted average
    total_score = sum(score * weight for _, score, weight in components)
    total_weight = sum(weight for _, _, weight in components)

    final_score = int(total_score / total_weight)

    return final_score


def _score_hrv_vs_baseline(profile_id: str) -> Optional[int]:
    """Score HRV relative to 28d baseline (0-100)"""
    with get_db() as conn:
        cur = get_cursor(conn)

        # Get recent HRV (last 3 days average)
        cur.execute("""
            SELECT AVG(hrv) as recent_hrv
            FROM vitals_baseline
            WHERE profile_id = %s
              AND hrv IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        recent_row = cur.fetchone()
        if not recent_row or not recent_row['recent_hrv']:
            return None

        recent_hrv = recent_row['recent_hrv']

        # Get baseline (28d average, excluding last 3 days)
        cur.execute("""
            SELECT AVG(hrv) as baseline_hrv
            FROM vitals_baseline
            WHERE profile_id = %s
              AND hrv IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '28 days'
              AND date < CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        baseline_row = cur.fetchone()
        if not baseline_row or not baseline_row['baseline_hrv']:
            return None

        baseline_hrv = baseline_row['baseline_hrv']

    # Calculate percentage deviation
    deviation_pct = ((recent_hrv - baseline_hrv) / baseline_hrv) * 100

    # Score: higher HRV = better recovery
    if deviation_pct >= 10:
        return 100
    elif deviation_pct >= 5:
        return 90
    elif deviation_pct >= 0:
        return 75
    elif deviation_pct >= -5:
        return 60
    elif deviation_pct >= -10:
        return 45
    else:
        return max(20, 45 + int(deviation_pct * 2))


def _score_rhr_vs_baseline(profile_id: str) -> Optional[int]:
    """Score RHR relative to 28d baseline (0-100)"""
    with get_db() as conn:
        cur = get_cursor(conn)

        # Get recent RHR (last 3 days average)
        cur.execute("""
            SELECT AVG(resting_hr) as recent_rhr
            FROM vitals_baseline
            WHERE profile_id = %s
              AND resting_hr IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        recent_row = cur.fetchone()
        if not recent_row or not recent_row['recent_rhr']:
            return None

        recent_rhr = recent_row['recent_rhr']

        # Get baseline (28d average, excluding last 3 days)
        cur.execute("""
            SELECT AVG(resting_hr) as baseline_rhr
            FROM vitals_baseline
            WHERE profile_id = %s
              AND resting_hr IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '28 days'
              AND date < CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        baseline_row = cur.fetchone()
        if not baseline_row or not baseline_row['baseline_rhr']:
            return None

        baseline_rhr = baseline_row['baseline_rhr']

    # Calculate difference (bpm)
    difference = recent_rhr - baseline_rhr

    # Score: lower RHR = better recovery
    if difference <= -3:
        return 100
    elif difference <= -1:
        return 90
    elif difference <= 1:
        return 75
    elif difference <= 3:
        return 60
    elif difference <= 5:
        return 45
    else:
        return max(20, 45 - (difference * 5))


def _score_sleep_duration(profile_id: str) -> Optional[int]:
    """Score recent sleep duration (0-100)"""
    avg_sleep_hours = calculate_sleep_avg_duration_7d(profile_id)

    if avg_sleep_hours is None:
        return None

    # Target: 7-9 hours
    if 7 <= avg_sleep_hours <= 9:
        return 100
    elif 6.5 <= avg_sleep_hours < 7:
        return 85
    elif 6 <= avg_sleep_hours < 6.5:
        return 70
    elif avg_sleep_hours >= 9.5:
        return 85  # Too much sleep can indicate fatigue
    else:
        return max(40, int(avg_sleep_hours * 10))


def _score_sleep_debt(profile_id: str) -> Optional[int]:
    """Score sleep debt (0-100)"""
    debt_hours = calculate_sleep_debt_hours(profile_id)

    if debt_hours is None:
        return None

    # Score based on accumulated debt
    if debt_hours <= 1:
        return 100
    elif debt_hours <= 3:
        return 85
    elif debt_hours <= 5:
        return 70
    elif debt_hours <= 8:
        return 55
    else:
        return max(30, 100 - (debt_hours * 8))


def _score_sleep_regularity(profile_id: str) -> Optional[int]:
    """Score sleep regularity (0-100)"""
    regularity_proxy = calculate_sleep_regularity_proxy(profile_id)

    if regularity_proxy is None:
        return None

    # regularity_proxy = mean absolute shift in minutes
    # Lower = better
    if regularity_proxy <= 30:
        return 100
    elif regularity_proxy <= 45:
        return 85
    elif regularity_proxy <= 60:
        return 70
    elif regularity_proxy <= 90:
        return 55
    else:
        return max(30, 100 - int(regularity_proxy / 2))


def _score_recent_load_balance(profile_id: str) -> Optional[int]:
    """Score recent training load balance (0-100)"""
    load_3d = calculate_recent_load_balance_3d(profile_id)

    if load_3d is None:
        return None

    # Proxy load: 0-300 = low, 300-600 = moderate, >600 = high
    if load_3d < 300:
        # Under-loading
        return 90
    elif load_3d <= 600:
        # Optimal
        return 100
    elif load_3d <= 900:
        # High but manageable
        return 75
    elif load_3d <= 1200:
        # Very high
        return 55
    else:
        # Excessive
        return max(30, 100 - (load_3d / 20))


def _score_recovery_data_quality(profile_id: str) -> Optional[int]:
    """Score data quality for recovery metrics (0-100)"""
    quality = calculate_recovery_data_quality(profile_id)
    return quality['overall_score']


# ============================================================================
# Individual Recovery Metrics
# ============================================================================

def calculate_hrv_vs_baseline_pct(profile_id: str) -> Optional[float]:
    """Calculate HRV deviation from baseline (percentage)"""
    with get_db() as conn:
        cur = get_cursor(conn)

        # Recent HRV (3d avg)
        cur.execute("""
            SELECT AVG(hrv) as recent_hrv
            FROM vitals_baseline
            WHERE profile_id = %s
              AND hrv IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        recent_row = cur.fetchone()
        if not recent_row or not recent_row['recent_hrv']:
            return None

        recent = recent_row['recent_hrv']

        # Baseline (28d avg, excluding last 3d)
        cur.execute("""
            SELECT AVG(hrv) as baseline_hrv
            FROM vitals_baseline
            WHERE profile_id = %s
              AND hrv IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '28 days'
              AND date < CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        baseline_row = cur.fetchone()
        if not baseline_row or not baseline_row['baseline_hrv']:
            return None

        baseline = baseline_row['baseline_hrv']

    deviation_pct = ((recent - baseline) / baseline) * 100
    return round(deviation_pct, 1)


def calculate_rhr_vs_baseline_pct(profile_id: str) -> Optional[float]:
    """Calculate RHR deviation from baseline (percentage)"""
    with get_db() as conn:
        cur = get_cursor(conn)

        # Recent RHR (3d avg)
        cur.execute("""
            SELECT AVG(resting_hr) as recent_rhr
            FROM vitals_baseline
            WHERE profile_id = %s
              AND resting_hr IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        recent_row = cur.fetchone()
        if not recent_row or not recent_row['recent_rhr']:
            return None

        recent = recent_row['recent_rhr']

        # Baseline
        cur.execute("""
            SELECT AVG(resting_hr) as baseline_rhr
            FROM vitals_baseline
            WHERE profile_id = %s
              AND resting_hr IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '28 days'
              AND date < CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        baseline_row = cur.fetchone()
        if not baseline_row or not baseline_row['baseline_rhr']:
            return None

        baseline = baseline_row['baseline_rhr']

    deviation_pct = ((recent - baseline) / baseline) * 100
    return round(deviation_pct, 1)


def calculate_sleep_avg_duration_7d(profile_id: str) -> Optional[float]:
    """Calculate average sleep duration (hours) last 7 days"""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT AVG(duration_minutes) as avg_sleep_min
            FROM sleep_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '7 days'
              AND duration_minutes IS NOT NULL
        """, (profile_id,))

        row = cur.fetchone()
        if not row or not row['avg_sleep_min']:
            return None

        avg_hours = row['avg_sleep_min'] / 60
        return round(avg_hours, 1)


def calculate_sleep_debt_hours(profile_id: str) -> Optional[float]:
    """
    Calculate accumulated sleep debt (hours) last 14 days
    Assumes 7.5h target per night
    """
    target_hours = 7.5

    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT duration_minutes
            FROM sleep_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '14 days'
              AND duration_minutes IS NOT NULL
            ORDER BY date DESC
        """, (profile_id,))

        sleep_data = [row['duration_minutes'] for row in cur.fetchall()]

        if len(sleep_data) < 10:  # Need at least 10 days
            return None

        # Calculate cumulative debt
        total_debt_min = sum(max(0, (target_hours * 60) - sleep_min) for sleep_min in sleep_data)
        debt_hours = total_debt_min / 60

        return round(debt_hours, 1)


def calculate_sleep_regularity_proxy(profile_id: str) -> Optional[float]:
    """
    Sleep regularity proxy: mean absolute shift from previous day (minutes)
    Lower = more regular
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT bedtime, wake_time, date
            FROM sleep_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '14 days'
              AND bedtime IS NOT NULL
              AND wake_time IS NOT NULL
            ORDER BY date
        """, (profile_id,))

        sleep_data = cur.fetchall()

        if len(sleep_data) < 7:
            return None

        # Calculate day-to-day shifts
        shifts = []
        for i in range(1, len(sleep_data)):
            prev = sleep_data[i-1]
            curr = sleep_data[i]

            # Bedtime shift (minutes)
            prev_bedtime = prev['bedtime']
            curr_bedtime = curr['bedtime']

            # Convert to minutes since midnight
            prev_bed_min = prev_bedtime.hour * 60 + prev_bedtime.minute
            curr_bed_min = curr_bedtime.hour * 60 + curr_bedtime.minute

            # Handle cross-midnight (e.g., 23:00 to 01:00)
            bed_shift = abs(curr_bed_min - prev_bed_min)
            if bed_shift > 720:  # More than 12 hours = wrapped around
                bed_shift = 1440 - bed_shift

            shifts.append(bed_shift)

        mean_shift = sum(shifts) / len(shifts)
        return round(mean_shift, 1)


def calculate_recent_load_balance_3d(profile_id: str) -> Optional[int]:
    """Calculate proxy internal load last 3 days"""
    from calculations.activity_metrics import calculate_proxy_internal_load_7d

    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT SUM(duration_min) as total_duration
            FROM activity_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '3 days'
        """, (profile_id,))

        row = cur.fetchone()
        if not row:
            return None

        # Simplified 3d load (duration-based)
        return int(row['total_duration'] or 0)


def calculate_sleep_quality_7d(profile_id: str) -> Optional[int]:
    """
    Calculate sleep quality score (0-100) based on deep+REM percentage
    Last 7 days
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT duration_minutes, deep_minutes, rem_minutes
            FROM sleep_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '7 days'
              AND duration_minutes IS NOT NULL
        """, (profile_id,))

        sleep_data = cur.fetchall()

        if len(sleep_data) < 4:
            return None

        quality_scores = []
        for s in sleep_data:
            dur = s["duration_minutes"]
            if not dur or dur <= 0:
                continue
            d = s["deep_minutes"]
            r = s["rem_minutes"]
            if d is None and r is None:
                continue
            di, ri = (d or 0), (r or 0)
            quality_pct = ((di + ri) / dur) * 100
            # 40-60% deep+REM is good
            if quality_pct >= 45:
                quality_scores.append(100)
            elif quality_pct >= 35:
                quality_scores.append(75)
            elif quality_pct >= 25:
                quality_scores.append(50)
            else:
                quality_scores.append(30)

        if not quality_scores:
            return None

        avg_quality = sum(quality_scores) / len(quality_scores)
        return int(avg_quality)


# ============================================================================
# Data Quality Assessment
# ============================================================================

def calculate_recovery_data_quality(profile_id: str) -> Dict[str, any]:
    """
    Assess data quality for recovery metrics
    Returns dict with quality score and details
    """
    with get_db() as conn:
        cur = get_cursor(conn)

        # HRV measurements (28d)
        cur.execute("""
            SELECT COUNT(*) as hrv_count
            FROM vitals_baseline
            WHERE profile_id = %s
              AND hrv IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '28 days'
        """, (profile_id,))
        hrv_count = cur.fetchone()['hrv_count']

        # RHR measurements (28d)
        cur.execute("""
            SELECT COUNT(*) as rhr_count
            FROM vitals_baseline
            WHERE profile_id = %s
              AND resting_hr IS NOT NULL
              AND date >= CURRENT_DATE - INTERVAL '28 days'
        """, (profile_id,))
        rhr_count = cur.fetchone()['rhr_count']

        # Sleep measurements (28d)
        cur.execute("""
            SELECT COUNT(*) as sleep_count
            FROM sleep_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '28 days'
        """, (profile_id,))
        sleep_count = cur.fetchone()['sleep_count']

    # Score components
    hrv_score = min(100, (hrv_count / 21) * 100)  # 21 = 75% coverage
    rhr_score = min(100, (rhr_count / 21) * 100)
    sleep_score = min(100, (sleep_count / 21) * 100)

    # Overall score
    overall_score = int(
        hrv_score * 0.3 +
        rhr_score * 0.3 +
        sleep_score * 0.4
    )

    if overall_score >= 80:
        confidence = "high"
    elif overall_score >= 60:
        confidence = "medium"
    else:
        confidence = "low"

    return {
        "overall_score": overall_score,
        "confidence": confidence,
        "measurements": {
            "hrv_28d": hrv_count,
            "rhr_28d": rhr_count,
            "sleep_28d": sleep_count
        },
        "component_scores": {
            "hrv": int(hrv_score),
            "rhr": int(rhr_score),
            "sleep": int(sleep_score)
        }
    }
