"""
Body Metrics Data Layer

Provides structured data for body composition and measurements.

Functions:
    - get_latest_weight_data(): Most recent weight entry
    - get_bmi_data(): BMI from latest weight + profile height
    - get_profile_goal_weight_data(): Zielgewicht (Profilfeld)
    - get_profile_goal_bf_pct_data(): Ziel-KFA % (Profilfeld)
    - get_weight_trend_data(): Weight trend with slope and direction
    - get_body_composition_data(): Body fat percentage and lean mass
    - get_circumference_summary_data(): Latest circumference measurements

All functions return structured data (dict) without formatting.
Use placeholder_resolver.py for formatted strings for AI.

Phase 0c: Multi-Layer Architecture
Version: 1.0
"""

from typing import Dict, List, Optional, Tuple
from datetime import datetime, timedelta, date
import statistics
from db import get_db, get_cursor, r2d
from data_layer.utils import calculate_confidence, safe_float


def get_latest_weight_data(
    profile_id: str
) -> Dict:
    """
    Get most recent weight entry.

    Args:
        profile_id: User profile ID

    Returns:
        {
            "weight": float,             # kg
            "date": date,
            "confidence": str
        }

    Migration from Phase 0b:
        OLD: get_latest_weight() returned formatted string "85.0 kg"
        NEW: Returns structured data {"weight": 85.0, "date": ...}
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """SELECT weight, date FROM weight_log
               WHERE profile_id=%s
               ORDER BY date DESC
               LIMIT 1""",
            (profile_id,)
        )
        row = cur.fetchone()

        if not row:
            return {
                "weight": 0.0,
                "date": None,
                "confidence": "insufficient"
            }

        return {
            "weight": safe_float(row['weight']),
            "date": row['date'],
            "confidence": "high"
        }


def get_bmi_data(profile_id: str) -> Dict:
    """
    BMI from latest weight_log entry and profiles.height (cm).

    Returns:
        {
            "bmi": float | None,
            "weight_kg": float | None,
            "height_cm": float | None,
            "confidence": "high" | "insufficient",
        }
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """
            SELECT pr.height,
                   (SELECT wl.weight FROM weight_log wl
                    WHERE wl.profile_id = pr.id
                    ORDER BY wl.date DESC
                    LIMIT 1) AS weight
            FROM profiles pr
            WHERE pr.id = %s
            """,
            (profile_id,),
        )
        row = cur.fetchone()
        if not row:
            return {
                "bmi": None,
                "weight_kg": None,
                "height_cm": None,
                "confidence": "insufficient",
            }

        height_cm = row["height"]
        weight = row["weight"]
        if height_cm is None or weight is None:
            return {
                "bmi": None,
                "weight_kg": safe_float(weight) if weight is not None else None,
                "height_cm": safe_float(height_cm) if height_cm is not None else None,
                "confidence": "insufficient",
            }

        h = safe_float(height_cm)
        w = safe_float(weight)
        if h <= 0:
            return {
                "bmi": None,
                "weight_kg": w,
                "height_cm": h,
                "confidence": "insufficient",
            }

        height_m = h / 100.0
        bmi = w / (height_m ** 2)
        return {
            "bmi": bmi,
            "weight_kg": w,
            "height_cm": h,
            "confidence": "high",
        }


def get_profile_goal_weight_data(profile_id: str) -> Dict:
    """Strategisches Zielgewicht aus profiles.goal_weight (kg), nicht goals-Tabelle."""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            "SELECT goal_weight FROM profiles WHERE id=%s",
            (profile_id,),
        )
        row = cur.fetchone()
        if not row or row.get("goal_weight") is None:
            return {"goal_weight_kg": None, "confidence": "insufficient"}
        return {
            "goal_weight_kg": safe_float(row["goal_weight"]),
            "confidence": "high",
        }


def get_profile_goal_bf_pct_data(profile_id: str) -> Dict:
    """Strategisches Ziel-KFA aus profiles.goal_bf_pct (%), nicht goals-Tabelle."""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            "SELECT goal_bf_pct FROM profiles WHERE id=%s",
            (profile_id,),
        )
        row = cur.fetchone()
        if not row or row.get("goal_bf_pct") is None:
            return {"goal_bf_pct": None, "confidence": "insufficient"}
        return {
            "goal_bf_pct": safe_float(row["goal_bf_pct"]),
            "confidence": "high",
        }


def get_weight_trend_data(
    profile_id: str,
    days: int = 28
) -> Dict:
    """
    Calculate weight trend with slope and direction.

    Args:
        profile_id: User profile ID
        days: Analysis window (default 28)

    Returns:
        {
            "first_value": float,
            "last_value": float,
            "delta": float,           # kg change
            "direction": str,         # "increasing" | "decreasing" | "stable"
            "data_points": int,
            "confidence": str,
            "days_analyzed": int,
            "first_date": date,
            "last_date": date,
            "series": [{"date": date, "weight": float}, ...],  # für Charts ohne zweites Query
        }

    Confidence Rules:
        - high: >= 18 points (28d) or >= 4 points (7d)
        - medium: >= 12 points (28d) or >= 3 points (7d)
        - low: >= 8 points (28d) or >= 2 points (7d)
        - insufficient: < thresholds

    Migration from Phase 0b:
        OLD: get_weight_trend() returned formatted string
        NEW: Returns structured data for reuse in charts + AI
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
        cur.execute(
            """SELECT weight, date FROM weight_log
               WHERE profile_id=%s AND date >= %s
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = [r2d(r) for r in cur.fetchall()]

        # Calculate confidence
        confidence = calculate_confidence(len(rows), days, "general")

        # Early return if insufficient
        if confidence == 'insufficient' or len(rows) < 2:
            return {
                "confidence": "insufficient",
                "data_points": len(rows),
                "days_analyzed": days,
                "first_value": 0.0,
                "last_value": 0.0,
                "delta": 0.0,
                "direction": "unknown",
                "first_date": None,
                "last_date": None,
                "series": [],
            }

        # Extract values
        first_value = safe_float(rows[0]['weight'])
        last_value = safe_float(rows[-1]['weight'])
        delta = last_value - first_value

        # Determine direction
        if abs(delta) < 0.3:
            direction = "stable"
        elif delta > 0:
            direction = "increasing"
        else:
            direction = "decreasing"

        return {
            "first_value": first_value,
            "last_value": last_value,
            "delta": delta,
            "direction": direction,
            "data_points": len(rows),
            "confidence": confidence,
            "days_analyzed": days,
            "first_date": rows[0]['date'],
            "last_date": rows[-1]['date'],
            "series": [
                {"date": r["date"], "weight": safe_float(r["weight"])}
                for r in rows
            ],
        }


def get_body_composition_data(
    profile_id: str,
    days: int = 90
) -> Dict:
    """
    Get latest body composition data (body fat, lean mass).

    Args:
        profile_id: User profile ID
        days: Lookback window (default 90)

    Returns:
        {
            "body_fat_pct": float,
            "method": str,            # "jackson_pollock" | "durnin_womersley" | etc.
            "date": date,
            "confidence": str,
            "data_points": int
        }

    Migration from Phase 0b:
        OLD: get_latest_bf() returned formatted string "15.2%"
        NEW: Returns structured data {"body_fat_pct": 15.2, ...}
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT body_fat_pct, sf_method, date
               FROM caliper_log
               WHERE profile_id=%s
                 AND body_fat_pct IS NOT NULL
                 AND date >= %s
               ORDER BY date DESC
               LIMIT 1""",
            (profile_id, cutoff)
        )
        row = r2d(cur.fetchone()) if cur.rowcount > 0 else None

        if not row:
            return {
                "confidence": "insufficient",
                "data_points": 0,
                "body_fat_pct": 0.0,
                "method": None,
                "date": None
            }

        return {
            "body_fat_pct": safe_float(row['body_fat_pct']),
            "method": row.get('sf_method', 'unknown'),
            "date": row['date'],
            "confidence": "high",  # Latest measurement is always high confidence
            "data_points": 1
        }


def get_circumference_summary_data(
    profile_id: str,
    max_age_days: int = 90
) -> Dict:
    """
    Get latest circumference measurements for all body points.

    For each measurement point, fetches the most recent value (even if from different dates).
    Returns measurements with age in days for each point.

    Args:
        profile_id: User profile ID
        max_age_days: Maximum age of measurements to include (default 90)

    Returns:
        {
            "measurements": [
                {
                    "point": str,        # "Nacken", "Brust", etc.
                    "field": str,        # "c_neck", "c_chest", etc.
                    "value": float,      # cm
                    "date": date,
                    "age_days": int
                },
                ...
            ],
            "confidence": str,
            "data_points": int,
            "newest_date": date,
            "oldest_date": date
        }

    Migration from Phase 0b:
        OLD: get_circ_summary() returned formatted string "Nacken 38.0cm (vor 2 Tagen), ..."
        NEW: Returns structured array for charts + AI formatting
    """
    with get_db() as conn:
        cur = get_cursor(conn)

        # Define all circumference points
        fields = [
            ('c_neck', 'Nacken'),
            ('c_chest', 'Brust'),
            ('c_waist', 'Taille'),
            ('c_belly', 'Bauch'),
            ('c_hip', 'Hüfte'),
            ('c_thigh', 'Oberschenkel'),
            ('c_calf', 'Wade'),
            ('c_arm', 'Arm')
        ]

        measurements = []
        today = datetime.now().date()

        # Get latest value for each field individually
        for field_name, label in fields:
            cur.execute(
                f"""SELECT {field_name}, date,
                           CURRENT_DATE - date AS age_days
                    FROM circumference_log
                    WHERE profile_id=%s
                      AND {field_name} IS NOT NULL
                      AND date >= %s
                    ORDER BY date DESC
                    LIMIT 1""",
                (profile_id, (today - timedelta(days=max_age_days)).isoformat())
            )
            row = r2d(cur.fetchone()) if cur.rowcount > 0 else None

            if row:
                measurements.append({
                    "point": label,
                    "field": field_name,
                    "value": safe_float(row[field_name]),
                    "date": row['date'],
                    "age_days": row['age_days']
                })

        # Calculate confidence based on how many points we have
        confidence = calculate_confidence(len(measurements), 8, "general")

        if not measurements:
            return {
                "measurements": [],
                "confidence": "insufficient",
                "data_points": 0,
                "newest_date": None,
                "oldest_date": None
            }

        # Find newest and oldest dates
        dates = [m['date'] for m in measurements]
        newest_date = max(dates)
        oldest_date = min(dates)

        return {
            "measurements": measurements,
            "confidence": confidence,
            "data_points": len(measurements),
            "newest_date": newest_date,
            "oldest_date": oldest_date
        }


# ============================================================================
# Calculated Metrics (migrated from calculations/body_metrics.py)
# Phase 0c: Single Source of Truth for KI + Charts
# ============================================================================

# ── Weight Trend Calculations ──────────────────────────────────────────────

def calculate_weight_7d_median(profile_id: str) -> Optional[float]:
    """Calculate 7-day median weight (reduces daily noise)"""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT weight
            FROM weight_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '7 days'
            ORDER BY date DESC
        """, (profile_id,))

        weights = [
            safe_float(row['weight'])
            for row in cur.fetchall()
            if row['weight'] is not None
        ]

        if len(weights) < 4:  # Need at least 4 measurements
            return None

        return round(float(statistics.median(weights)), 1)


def calculate_weight_28d_slope(profile_id: str) -> Optional[float]:
    """Calculate 28-day weight slope (kg/day)"""
    return _calculate_weight_slope(profile_id, days=28)


def calculate_weight_90d_slope(profile_id: str) -> Optional[float]:
    """Calculate 90-day weight slope (kg/day)"""
    return _calculate_weight_slope(profile_id, days=90)


def _calculate_weight_slope(profile_id: str, days: int) -> Optional[float]:
    """
    Calculate weight slope using linear regression
    Returns kg/day (negative = weight loss)
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT date, weight
            FROM weight_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '%s days'
            ORDER BY date
        """, (profile_id, days))

        data = [
            (row['date'], safe_float(row['weight']))
            for row in cur.fetchall()
            if row['weight'] is not None
        ]

        # Need minimum data points based on period
        min_points = max(18, int(days * 0.6))  # 60% coverage
        if len(data) < min_points:
            return None

        # Convert dates to days since start
        start_date = data[0][0]
        x_values = [(date - start_date).days for date, _ in data]
        y_values = [w for _, w in data]

        # Linear regression (alles float: PostgreSQL numeric → Decimal in Python)
        n = len(data)
        x_mean = float(sum(x_values)) / n
        y_mean = float(sum(y_values)) / n

        numerator = sum(float(x - x_mean) * float(y - y_mean) for x, y in zip(x_values, y_values))
        denominator = float(sum((x - x_mean) ** 2 for x in x_values))

        if denominator == 0:
            return None

        slope = numerator / denominator
        return round(float(slope), 4)  # kg/day


def calculate_goal_projection_date(profile_id: str, goal_id: str) -> Optional[str]:
    """
    Calculate projected date to reach goal based on 28d trend
    Returns ISO date string or None if unrealistic
    """
    from goal_utils import get_goal_by_id

    goal = get_goal_by_id(goal_id)
    if not goal or goal['goal_type'] != 'weight':
        return None

    slope = calculate_weight_28d_slope(profile_id)
    if not slope or slope == 0:
        return None

    current = goal['current_value']
    target = goal['target_value']
    remaining = target - current

    days_needed = remaining / slope

    # Unrealistic if >2 years or negative
    if days_needed < 0 or days_needed > 730:
        return None

    projection_date = datetime.now().date() + timedelta(days=int(days_needed))
    return projection_date.isoformat()


def calculate_goal_progress_pct(current: float, target: float, start: float) -> int:
    """
    Calculate goal progress percentage
    Returns 0-100 (can exceed 100 if target surpassed)
    """
    if start == target:
        return 100 if current == target else 0

    progress = ((current - start) / (target - start)) * 100
    return max(0, min(100, int(progress)))


# ── Fat Mass / Lean Mass Calculations ───────────────────────────────────────

def calculate_fm_28d_change(profile_id: str) -> Optional[float]:
    """Calculate 28-day fat mass change (kg)"""
    return _calculate_body_composition_change(profile_id, 'fm', 28)


def calculate_lbm_28d_change(profile_id: str) -> Optional[float]:
    """Calculate 28-day lean body mass change (kg)"""
    return _calculate_body_composition_change(profile_id, 'lbm', 28)


def _calculate_body_composition_change(profile_id: str, metric: str, days: int) -> Optional[float]:
    """
    Calculate change in body composition over period
    metric: 'fm' (fat mass) or 'lbm' (lean mass)
    """
    with get_db() as conn:
        cur = get_cursor(conn)

        # Get weight and caliper measurements
        cur.execute("""
            SELECT w.date, w.weight, c.body_fat_pct
            FROM weight_log w
            LEFT JOIN caliper_log c ON w.profile_id = c.profile_id
                AND w.date = c.date
            WHERE w.profile_id = %s
              AND w.date >= CURRENT_DATE - INTERVAL '%s days'
            ORDER BY w.date DESC
        """, (profile_id, days))

        data = [
            {
                'date': row['date'],
                'weight': row['weight'],
                'bf_pct': row['body_fat_pct']
            }
            for row in cur.fetchall()
            if row['body_fat_pct'] is not None
        ]

        if len(data) < 2:
            return None

        # Most recent and oldest measurement
        recent = data[0]
        oldest = data[-1]

        # Calculate FM and LBM (DB numeric → Decimal; für Regression/Scores nur float)
        rw = float(safe_float(recent['weight']) or 0)
        ob = float(safe_float(recent['bf_pct']) or 0)
        ow = float(safe_float(oldest['weight']) or 0)
        obf = float(safe_float(oldest['bf_pct']) or 0)

        recent_fm = rw * (ob / 100)
        recent_lbm = rw - recent_fm

        oldest_fm = ow * (obf / 100)
        oldest_lbm = ow - oldest_fm

        if metric == 'fm':
            change = recent_fm - oldest_fm
        else:
            change = recent_lbm - oldest_lbm

        return round(float(change), 2)


# ── Circumference Calculations ──────────────────────────────────────────────

def calculate_waist_28d_delta(profile_id: str) -> Optional[float]:
    """Calculate 28-day waist circumference change (cm)"""
    return _calculate_circumference_delta(profile_id, 'c_waist', 28)


def calculate_hip_28d_delta(profile_id: str) -> Optional[float]:
    """Calculate 28-day hip circumference change (cm)"""
    return _calculate_circumference_delta(profile_id, 'c_hip', 28)


def calculate_chest_28d_delta(profile_id: str) -> Optional[float]:
    """Calculate 28-day chest circumference change (cm)"""
    return _calculate_circumference_delta(profile_id, 'c_chest', 28)


def calculate_arm_28d_delta(profile_id: str) -> Optional[float]:
    """Calculate 28-day arm circumference change (cm)"""
    return _calculate_circumference_delta(profile_id, 'c_arm', 28)


def calculate_thigh_28d_delta(profile_id: str) -> Optional[float]:
    """Calculate 28-day thigh circumference change (cm)"""
    delta = _calculate_circumference_delta(profile_id, 'c_thigh', 28)
    if delta is None:
        return None
    return round(delta, 1)


def _calculate_circumference_delta(profile_id: str, column: str, days: int) -> Optional[float]:
    """Calculate change in circumference measurement"""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(f"""
            SELECT {column}
            FROM circumference_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '%s days'
              AND {column} IS NOT NULL
            ORDER BY date DESC
            LIMIT 1
        """, (profile_id, days))

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

        cur.execute(f"""
            SELECT {column}
            FROM circumference_log
            WHERE profile_id = %s
              AND date < CURRENT_DATE - INTERVAL '%s days'
              AND {column} IS NOT NULL
            ORDER BY date DESC
            LIMIT 1
        """, (profile_id, days))

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

        change = recent[column] - oldest[column]
        return round(change, 1)


def calculate_waist_hip_ratio(profile_id: str) -> Optional[float]:
    """Calculate current waist-to-hip ratio"""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT c_waist, c_hip
            FROM circumference_log
            WHERE profile_id = %s
              AND c_waist IS NOT NULL
              AND c_hip IS NOT NULL
            ORDER BY date DESC
            LIMIT 1
        """, (profile_id,))

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

        ratio = row['c_waist'] / row['c_hip']
        return round(ratio, 3)


# ── Recomposition Detector ───────────────────────────────────────────────────

def calculate_recomposition_quadrant(profile_id: str) -> Optional[str]:
    """
    Determine recomposition quadrant based on 28d changes:
    - optimal: FM down, LBM up
    - cut_with_risk: FM down, LBM down
    - bulk: FM up, LBM up
    - unfavorable: FM up, LBM down
    """
    fm_change = calculate_fm_28d_change(profile_id)
    lbm_change = calculate_lbm_28d_change(profile_id)

    if fm_change is None or lbm_change is None:
        return None

    if fm_change < 0 and lbm_change > 0:
        return "optimal"
    elif fm_change < 0 and lbm_change < 0:
        return "cut_with_risk"
    elif fm_change > 0 and lbm_change > 0:
        return "bulk"
    else:
        return "unfavorable"


# ── Body Progress Score ───────────────────────────────────────────────────────

def calculate_body_progress_score(profile_id: str, focus_weights: Optional[Dict] = None) -> Optional[int]:
    """Calculate body progress score (0-100) weighted by user's focus areas"""
    if focus_weights is None:
        from data_layer.scores import get_user_focus_weights
        focus_weights = get_user_focus_weights(profile_id)

    weight_loss = float(focus_weights.get('weight_loss', 0) or 0)
    muscle_gain = float(focus_weights.get('muscle_gain', 0) or 0)
    body_recomp = float(focus_weights.get('body_recomposition', 0) or 0)

    total_body_weight = weight_loss + muscle_gain + body_recomp

    if total_body_weight == 0:
        return None

    components = []

    if weight_loss > 0:
        weight_score = _score_weight_trend(profile_id)
        if weight_score is not None:
            components.append(('weight', weight_score, weight_loss))

    if muscle_gain > 0 or body_recomp > 0:
        comp_score = _score_body_composition(profile_id)
        if comp_score is not None:
            components.append(('composition', comp_score, muscle_gain + body_recomp))

    waist_score = _score_waist_trend(profile_id)
    if waist_score is not None:
        waist_weight = 20 + (weight_loss * 0.3)
        components.append(('waist', waist_score, waist_weight))

    if not components:
        return None

    total_score = sum(float(score) * float(weight) for _, score, weight in components)
    total_weight = sum(float(weight) for _, _, weight in components)

    return int(total_score / total_weight)


def _score_weight_trend(profile_id: str) -> Optional[int]:
    """Score weight trend alignment with goals (0-100)"""
    from goal_utils import get_active_goals

    goals = get_active_goals(profile_id)
    weight_goals = [g for g in goals if g.get('goal_type') == 'weight']
    if not weight_goals:
        return None

    goal = next((g for g in weight_goals if g.get('is_primary')), weight_goals[0])

    current = goal.get('current_value')
    target = goal.get('target_value')
    start = goal.get('start_value')

    if None in [current, target]:
        return None

    current = float(current)
    target = float(target)

    if start is None:
        with get_db() as conn:
            cur = get_cursor(conn)
            cur.execute("""
                SELECT weight
                FROM weight_log
                WHERE profile_id = %s
                  AND date >= CURRENT_DATE - INTERVAL '90 days'
                ORDER BY date ASC
                LIMIT 1
            """, (profile_id,))
            row = cur.fetchone()
            start = float(row['weight']) if row else current
    else:
        start = float(start)

    progress_pct = calculate_goal_progress_pct(current, target, start)

    slope = calculate_weight_28d_slope(profile_id)
    if slope is not None:
        desired_direction = -1 if target < start else 1
        actual_direction = -1 if slope < 0 else 1

        if desired_direction == actual_direction:
            score = min(100, progress_pct + 10)
        else:
            score = max(0, progress_pct - 20)
    else:
        score = progress_pct

    return int(score)


def _score_body_composition(profile_id: str) -> Optional[int]:
    """Score body composition changes (0-100)"""
    fm_change = calculate_fm_28d_change(profile_id)
    lbm_change = calculate_lbm_28d_change(profile_id)

    if fm_change is None or lbm_change is None:
        return None

    quadrant = calculate_recomposition_quadrant(profile_id)

    if quadrant == "optimal":
        return 100
    elif quadrant == "cut_with_risk":
        penalty = min(30, abs(lbm_change) * 15)
        return max(50, 80 - int(penalty))
    elif quadrant == "bulk":
        if lbm_change > 0 and fm_change > 0:
            ratio = lbm_change / fm_change
            if ratio >= 3:
                return 90
            elif ratio >= 2:
                return 75
            elif ratio >= 1:
                return 60
            else:
                return 45
        return 60
    else:
        return 20


def _score_waist_trend(profile_id: str) -> Optional[int]:
    """Score waist circumference trend (0-100)"""
    delta = calculate_waist_28d_delta(profile_id)

    if delta is None:
        return None

    if delta <= -3:
        return 100
    elif delta <= -2:
        return 90
    elif delta <= -1:
        return 80
    elif delta <= 0:
        return 70
    elif delta <= 1:
        return 55
    elif delta <= 2:
        return 40
    else:
        return 20


# ── Data Quality Assessment ───────────────────────────────────────────────────

def calculate_body_data_quality(profile_id: str) -> Dict[str, any]:
    """Assess data quality for body metrics"""
    with get_db() as conn:
        cur = get_cursor(conn)

        cur.execute("""
            SELECT COUNT(*) as count
            FROM weight_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '28 days'
        """, (profile_id,))
        weight_count = cur.fetchone()['count']

        cur.execute("""
            SELECT COUNT(*) as count
            FROM caliper_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '28 days'
        """, (profile_id,))
        caliper_count = cur.fetchone()['count']

        cur.execute("""
            SELECT COUNT(*) as count
            FROM circumference_log
            WHERE profile_id = %s
              AND date >= CURRENT_DATE - INTERVAL '28 days'
        """, (profile_id,))
        circ_count = cur.fetchone()['count']

    weight_score = min(100, (weight_count / 18) * 100)
    caliper_score = min(100, (caliper_count / 4) * 100)
    circ_score = min(100, (circ_count / 4) * 100)

    overall_score = int(
        weight_score * 0.5 +
        caliper_score * 0.3 +
        circ_score * 0.2
    )

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

    return {
        "overall_score": overall_score,
        "confidence": confidence,
        "measurements": {
            "weight_28d": weight_count,
            "caliper_28d": caliper_count,
            "circumference_28d": circ_count
        },
        "component_scores": {
            "weight": int(weight_score),
            "caliper": int(caliper_score),
            "circumference": int(circ_score)
        }
    }
