"""
Goals Router - Core Goal CRUD & Focus Areas (Streamlined v2.0)

Endpoints for managing:
- Strategic focus areas (weighted multi-goal system)
- Tactical goal targets (concrete values with deadlines)
- Grouped goal views

Part of v9h Goal System (Phase 0a).

NOTE: Code split complete! Related endpoints moved to:
- goal_types.py → Goal Type Definitions (Admin CRUD)
- goal_progress.py → Progress tracking
- training_phases.py → Training phase management
- fitness_tests.py → Fitness test recording
"""
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import Optional, List
from datetime import date, timedelta

from db import get_db, get_cursor, r2d
from auth import require_auth
from goal_utils import get_current_value_for_goal

router = APIRouter(prefix="/api/goals", tags=["goals"])


def serialize_dates(obj):
    """Convert date/datetime objects to ISO format strings for JSON serialization."""
    if obj is None:
        return None
    if isinstance(obj, dict):
        return {k: serialize_dates(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [serialize_dates(item) for item in obj]
    if isinstance(obj, (date,)):
        return obj.isoformat()
    return obj

# ============================================================================
# Pydantic Models
# ============================================================================

class GoalModeUpdate(BaseModel):
    """Update strategic goal mode (deprecated - use FocusAreasUpdate)"""
    goal_mode: str  # weight_loss, strength, endurance, recomposition, health

class FocusAreasUpdate(BaseModel):
    """Update focus area weights (v2.0)"""
    weight_loss_pct: int
    muscle_gain_pct: int
    strength_pct: int
    endurance_pct: int
    flexibility_pct: int
    health_pct: int

class FocusContribution(BaseModel):
    """Focus area contribution (v2.0)"""
    focus_area_id: str
    contribution_weight: float = 100.0  # 0-100%

class GoalCreate(BaseModel):
    """Create or update a concrete goal"""
    goal_type: str  # weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr
    is_primary: bool = False  # Kept for backward compatibility
    target_value: float
    unit: str  # kg, %, ml/kg/min, bpm, mmHg, cm, reps
    target_date: Optional[date] = None
    start_date: Optional[date] = None  # When goal started (defaults to today, can be historical)
    start_value: Optional[float] = None  # Auto-populated from start_date if not provided
    category: Optional[str] = 'other'  # body, training, nutrition, recovery, health, other
    priority: Optional[int] = 2  # 1=high, 2=medium, 3=low
    name: Optional[str] = None
    description: Optional[str] = None
    focus_contributions: Optional[List[FocusContribution]] = []  # v2.0: Many-to-Many

class GoalUpdate(BaseModel):
    """Update existing goal"""
    target_value: Optional[float] = None
    target_date: Optional[date] = None
    start_date: Optional[date] = None  # Change start date (recalculates start_value)
    start_value: Optional[float] = None  # Manually override start value
    status: Optional[str] = None  # active, reached, abandoned, expired
    is_primary: Optional[bool] = None  # Kept for backward compatibility
    category: Optional[str] = None  # body, training, nutrition, recovery, health, other
    priority: Optional[int] = None  # 1=high, 2=medium, 3=low
    name: Optional[str] = None
    description: Optional[str] = None
    focus_contributions: Optional[List[FocusContribution]] = None  # v2.0: Many-to-Many

# ============================================================================
# Strategic Layer: Goal Modes
# ============================================================================

@router.get("/mode")
def get_goal_mode(session: dict = Depends(require_auth)):
    """Get user's current strategic goal mode"""
    pid = session['profile_id']

    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            "SELECT goal_mode FROM profiles WHERE id = %s",
            (pid,)
        )
        row = cur.fetchone()
        if not row:
            raise HTTPException(status_code=404, detail="Profil nicht gefunden")

        return {
            "goal_mode": row['goal_mode'] or 'health',
            "description": _get_goal_mode_description(row['goal_mode'] or 'health')
        }

@router.put("/mode")
def update_goal_mode(data: GoalModeUpdate, session: dict = Depends(require_auth)):
    """Update user's strategic goal mode"""
    pid = session['profile_id']

    # Validate goal mode
    valid_modes = ['weight_loss', 'strength', 'endurance', 'recomposition', 'health']
    if data.goal_mode not in valid_modes:
        raise HTTPException(
            status_code=400,
            detail=f"Ungültiger Goal Mode. Erlaubt: {', '.join(valid_modes)}"
        )

    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            "UPDATE profiles SET goal_mode = %s WHERE id = %s",
            (data.goal_mode, pid)
        )

        return {
            "goal_mode": data.goal_mode,
            "description": _get_goal_mode_description(data.goal_mode)
        }

def _get_goal_mode_description(mode: str) -> str:
    """Get description for goal mode"""
    descriptions = {
        'weight_loss': 'Gewichtsreduktion (Kaloriendefizit, Fettabbau)',
        'strength': 'Kraftaufbau (Muskelwachstum, progressive Belastung)',
        'endurance': 'Ausdauer (VO2Max, aerobe Kapazität)',
        'recomposition': 'Körperkomposition (gleichzeitig Fett ab- und Muskeln aufbauen)',
        'health': 'Allgemeine Gesundheit (ausgewogen, präventiv)'
    }
    return descriptions.get(mode, 'Unbekannt')

# ============================================================================
# Focus Areas (v2.0): Weighted Multi-Goal System
# ============================================================================

@router.get("/focus-areas")
def get_focus_areas(session: dict = Depends(require_auth)):
    """
    Get current focus area weights.

    Returns custom weights if set, otherwise derives from goal_mode.
    """
    pid = session['profile_id']

    with get_db() as conn:
        cur = get_cursor(conn)

        # Try to get custom focus areas (user_focus_preferences after Migration 031)
        try:
            cur.execute("""
                SELECT weight_loss_pct, muscle_gain_pct, strength_pct,
                       endurance_pct, flexibility_pct, health_pct,
                       created_at, updated_at
                FROM user_focus_preferences
                WHERE profile_id = %s
                LIMIT 1
            """, (pid,))
            row = cur.fetchone()
        except Exception as e:
            # Migration 031 not applied yet, try old table name
            print(f"[WARNING] user_focus_preferences not found, trying old focus_areas: {e}")
            try:
                cur.execute("""
                    SELECT weight_loss_pct, muscle_gain_pct, strength_pct,
                           endurance_pct, flexibility_pct, health_pct,
                           created_at, updated_at
                    FROM focus_areas
                    WHERE profile_id = %s AND active = true
                    LIMIT 1
                """, (pid,))
                row = cur.fetchone()
            except:
                row = None

        if row:
            return {
                "custom": True,
                "weight_loss_pct": row['weight_loss_pct'],
                "muscle_gain_pct": row['muscle_gain_pct'],
                "strength_pct": row['strength_pct'],
                "endurance_pct": row['endurance_pct'],
                "flexibility_pct": row['flexibility_pct'],
                "health_pct": row['health_pct'],
                "updated_at": row['updated_at']
            }

        # Fallback: Derive from goal_mode
        cur.execute("SELECT goal_mode FROM profiles WHERE id = %s", (pid,))
        profile = cur.fetchone()

        if not profile or not profile['goal_mode']:
            # Default balanced health
            return {
                "custom": False,
                "weight_loss_pct": 0,
                "muscle_gain_pct": 0,
                "strength_pct": 10,
                "endurance_pct": 20,
                "flexibility_pct": 15,
                "health_pct": 55,
                "source": "default"
            }

        # Derive from goal_mode (using same logic as migration)
        mode = profile['goal_mode']
        mode_mappings = {
            'weight_loss': {
                'weight_loss_pct': 60,
                'muscle_gain_pct': 0,
                'strength_pct': 10,
                'endurance_pct': 20,
                'flexibility_pct': 5,
                'health_pct': 5
            },
            'strength': {
                'weight_loss_pct': 0,
                'muscle_gain_pct': 40,
                'strength_pct': 50,
                'endurance_pct': 10,
                'flexibility_pct': 0,
                'health_pct': 0
            },
            'endurance': {
                'weight_loss_pct': 0,
                'muscle_gain_pct': 0,
                'strength_pct': 0,
                'endurance_pct': 70,
                'flexibility_pct': 10,
                'health_pct': 20
            },
            'recomposition': {
                'weight_loss_pct': 30,
                'muscle_gain_pct': 30,
                'strength_pct': 25,
                'endurance_pct': 10,
                'flexibility_pct': 5,
                'health_pct': 0
            },
            'health': {
                'weight_loss_pct': 0,
                'muscle_gain_pct': 0,
                'strength_pct': 10,
                'endurance_pct': 20,
                'flexibility_pct': 15,
                'health_pct': 55
            }
        }

        mapping = mode_mappings.get(mode, mode_mappings['health'])
        mapping['custom'] = False
        mapping['source'] = f"goal_mode:{mode}"
        return mapping

@router.put("/focus-areas")
def update_focus_areas(data: FocusAreasUpdate, session: dict = Depends(require_auth)):
    """
    Update focus area weights (upsert).

    Validates that sum = 100 and all values are 0-100.
    """
    pid = session['profile_id']

    # Validate sum = 100
    total = (
        data.weight_loss_pct + data.muscle_gain_pct + data.strength_pct +
        data.endurance_pct + data.flexibility_pct + data.health_pct
    )

    if total != 100:
        raise HTTPException(
            status_code=400,
            detail=f"Summe muss 100% sein (aktuell: {total}%)"
        )

    # Validate range 0-100
    values = [
        data.weight_loss_pct, data.muscle_gain_pct, data.strength_pct,
        data.endurance_pct, data.flexibility_pct, data.health_pct
    ]

    if any(v < 0 or v > 100 for v in values):
        raise HTTPException(
            status_code=400,
            detail="Alle Werte müssen zwischen 0 und 100 liegen"
        )

    with get_db() as conn:
        cur = get_cursor(conn)

        # Deactivate old focus_areas
        cur.execute(
            "UPDATE focus_areas SET active = false WHERE profile_id = %s",
            (pid,)
        )

        # Insert new focus_areas
        cur.execute("""
            INSERT INTO focus_areas (
                profile_id, weight_loss_pct, muscle_gain_pct, strength_pct,
                endurance_pct, flexibility_pct, health_pct
            ) VALUES (%s, %s, %s, %s, %s, %s, %s)
            RETURNING id
        """, (
            pid, data.weight_loss_pct, data.muscle_gain_pct, data.strength_pct,
            data.endurance_pct, data.flexibility_pct, data.health_pct
        ))

        return {
            "message": "Fokus-Bereiche aktualisiert",
            "weight_loss_pct": data.weight_loss_pct,
            "muscle_gain_pct": data.muscle_gain_pct,
            "strength_pct": data.strength_pct,
            "endurance_pct": data.endurance_pct,
            "flexibility_pct": data.flexibility_pct,
            "health_pct": data.health_pct
        }

# ============================================================================
# Tactical Layer: Concrete Goals - Core CRUD
# ============================================================================

@router.get("/list")
def list_goals(session: dict = Depends(require_auth)):
    """List all goals for current user"""
    pid = session['profile_id']

    try:
        with get_db() as conn:
            cur = get_cursor(conn)
            cur.execute("""
                SELECT id, goal_type, is_primary, status,
                       target_value, current_value, start_value, unit,
                       start_date, target_date, reached_date,
                       name, description,
                       progress_pct, projection_date, on_track,
                       created_at, updated_at
                FROM goals
                WHERE profile_id = %s
                ORDER BY is_primary DESC, created_at DESC
            """, (pid,))

            goals = [r2d(row) for row in cur.fetchall()]

            # Debug: Show first goal with dates
            if goals:
                first = goals[0]

            # Update current values for each goal
            for goal in goals:
                try:
                    _update_goal_progress(conn, pid, goal)
                except Exception as e:
                    print(f"[ERROR] Failed to update progress for goal {goal.get('id')}: {e}")
                    # Continue with other goals even if one fails

            # Serialize date objects to ISO format strings
            goals = serialize_dates(goals)

            return goals

    except Exception as e:
        print(f"[ERROR] list_goals failed: {e}")
        import traceback
        traceback.print_exc()
        raise HTTPException(
            status_code=500,
            detail=f"Fehler beim Laden der Ziele: {str(e)}"
        )

@router.post("/create")
def create_goal(data: GoalCreate, session: dict = Depends(require_auth)):
    """Create new goal"""
    pid = session['profile_id']

    with get_db() as conn:
        cur = get_cursor(conn)

        # If this is set as primary, unset other primary goals
        if data.is_primary:
            cur.execute(
                "UPDATE goals SET is_primary = false WHERE profile_id = %s",
                (pid,)
            )

        # Get current value for this goal type
        current_value = _get_current_value_for_goal_type(conn, pid, data.goal_type)

        # Determine start_date (default to today if not provided)
        start_date = data.start_date if data.start_date else date.today()

        # Determine start_value
        if data.start_value is not None:
            # User explicitly provided start_value
            start_value = data.start_value
        elif start_date < date.today():
            # Historical start date - try to get historical value
            historical_data = _get_historical_value_for_goal_type(conn, pid, data.goal_type, start_date)
            if historical_data is not None:
                # Use the actual measurement date and value
                start_date = historical_data['date']
                start_value = historical_data['value']
                print(f"[INFO] Auto-adjusted start_date to {start_date} (first measurement)")
            else:
                # No data found, fall back to current value and keep original date
                start_value = current_value
                print(f"[WARN] No historical data for {data.goal_type} on or after {start_date}, using current value")
        else:
            # Start date is today, use current value
            start_value = current_value

        # Insert goal
        cur.execute("""
            INSERT INTO goals (
                profile_id, goal_type, is_primary,
                target_value, current_value, start_value, unit,
                start_date, target_date, category, priority, name, description
            ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            RETURNING id
        """, (
            pid, data.goal_type, data.is_primary,
            data.target_value, current_value, start_value, data.unit,
            start_date, data.target_date, data.category, data.priority, data.name, data.description
        ))

        goal_id = cur.fetchone()['id']

        # v2.0: Insert focus area contributions
        if data.focus_contributions:
            for contrib in data.focus_contributions:
                cur.execute("""
                    INSERT INTO goal_focus_contributions
                        (goal_id, focus_area_id, contribution_weight)
                    VALUES (%s, %s, %s)
                    ON CONFLICT (goal_id, focus_area_id) DO UPDATE
                    SET contribution_weight = EXCLUDED.contribution_weight
                """, (goal_id, contrib.focus_area_id, contrib.contribution_weight))

        return {"id": goal_id, "message": "Ziel erstellt"}

@router.put("/{goal_id}")
def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_auth)):
    """Update existing goal"""
    pid = session['profile_id']

    with get_db() as conn:
        cur = get_cursor(conn)

        # Verify ownership
        cur.execute(
            "SELECT id FROM goals WHERE id = %s AND profile_id = %s",
            (goal_id, pid)
        )
        if not cur.fetchone():
            raise HTTPException(status_code=404, detail="Ziel nicht gefunden")

        # If setting this goal as primary, unset all other primary goals
        if data.is_primary is True:
            cur.execute(
                "UPDATE goals SET is_primary = false WHERE profile_id = %s AND id != %s",
                (pid, goal_id)
            )

        # Build update query dynamically
        updates = []
        params = []

        if data.target_value is not None:
            updates.append("target_value = %s")
            params.append(data.target_value)

        if data.target_date is not None:
            updates.append("target_date = %s")
            params.append(data.target_date)

        if data.status is not None:
            updates.append("status = %s")
            params.append(data.status)
            if data.status == 'reached':
                updates.append("reached_date = CURRENT_DATE")

        if data.is_primary is not None:
            updates.append("is_primary = %s")
            params.append(data.is_primary)

        if data.category is not None:
            updates.append("category = %s")
            params.append(data.category)

        if data.priority is not None:
            updates.append("priority = %s")
            params.append(data.priority)

        if data.name is not None:
            updates.append("name = %s")
            params.append(data.name)

        if data.description is not None:
            updates.append("description = %s")
            params.append(data.description)

        # Handle start_date and start_value
        # Determine what start_date and start_value to use
        final_start_date = None
        final_start_value = None

        if data.start_date is not None:
            # User provided a start_date
            requested_date = data.start_date

            # If start_value not explicitly provided, try to get historical value
            if data.start_value is None:
                # Get goal_type for historical lookup
                cur.execute("SELECT goal_type FROM goals WHERE id = %s", (goal_id,))
                goal_row = cur.fetchone()
                if goal_row:
                    goal_type = goal_row['goal_type']
                    historical_data = _get_historical_value_for_goal_type(conn, pid, goal_type, requested_date)

                    if historical_data is not None:
                        # Use actual measurement date and value
                        final_start_date = historical_data['date']
                        final_start_value = historical_data['value']
                        print(f"[INFO] Auto-adjusted to first measurement: {final_start_date} = {final_start_value}")
                    else:
                        # No historical data found, use requested date without value
                        final_start_date = requested_date
                        print(f"[WARN] No historical data found for {goal_type} on or after {requested_date}")
                else:
                    print(f"[ERROR] Could not find goal with id {goal_id}")
                    final_start_date = requested_date
            else:
                # User provided both date and value
                final_start_date = requested_date
                final_start_value = data.start_value

        elif data.start_value is not None:
            # Only start_value provided (no date)
            final_start_value = data.start_value

        # Add to updates if we have values
        if final_start_date is not None:
            updates.append("start_date = %s")
            params.append(final_start_date)

        if final_start_value is not None:
            updates.append("start_value = %s")
            params.append(final_start_value)

        # Handle focus_contributions separately (can be updated even if no other changes)
        if data.focus_contributions is not None:
            # Delete existing contributions
            cur.execute(
                "DELETE FROM goal_focus_contributions WHERE goal_id = %s",
                (goal_id,)
            )

            # Insert new contributions
            for contrib in data.focus_contributions:
                cur.execute("""
                    INSERT INTO goal_focus_contributions
                        (goal_id, focus_area_id, contribution_weight)
                    VALUES (%s, %s, %s)
                """, (goal_id, contrib.focus_area_id, contrib.contribution_weight))

        if not updates and data.focus_contributions is None:
            raise HTTPException(status_code=400, detail="Keine Änderungen angegeben")

        if updates:
            updates.append("updated_at = NOW()")
            params.extend([goal_id, pid])

            update_sql = f"UPDATE goals SET {', '.join(updates)} WHERE id = %s AND profile_id = %s"

            cur.execute(update_sql, tuple(params))

            # Verify what was actually saved
            cur.execute("""
                SELECT id, goal_type, start_date, start_value, target_date, target_value
                FROM goals WHERE id = %s
            """, (goal_id,))
            saved_goal = cur.fetchone()

        return {"message": "Ziel aktualisiert"}

@router.delete("/{goal_id}")
def delete_goal(goal_id: str, session: dict = Depends(require_auth)):
    """Delete goal"""
    pid = session['profile_id']

    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            "DELETE FROM goals WHERE id = %s AND profile_id = %s",
            (goal_id, pid)
        )

        if cur.rowcount == 0:
            raise HTTPException(status_code=404, detail="Ziel nicht gefunden")

        return {"message": "Ziel gelöscht"}

@router.get("/grouped")
def get_goals_grouped(session: dict = Depends(require_auth)):
    """
    Get all goals grouped by category.

    Returns structure:
    {
      "body": [{"id": "...", "goal_type": "weight", "priority": 1, ...}, ...],
      "training": [...],
      "nutrition": [...],
      "recovery": [...],
      "health": [...],
      "other": [...]
    }
    """
    pid = session['profile_id']

    with get_db() as conn:
        cur = get_cursor(conn)

        # Get all active goals with type definitions
        cur.execute("""
            SELECT
                g.id, g.goal_type, g.target_value, g.current_value, g.start_value,
                g.unit, g.start_date, g.target_date, g.reached_date, g.status,
                g.is_primary, g.category, g.priority,
                g.name, g.description, g.progress_pct, g.on_track, g.projection_date,
                g.created_at, g.updated_at,
                gt.label_de, gt.icon, gt.category as type_category,
                gt.source_table, gt.source_column
            FROM goals g
            LEFT JOIN goal_type_definitions gt ON g.goal_type = gt.type_key
            WHERE g.profile_id = %s
            ORDER BY g.category, g.priority ASC, g.created_at DESC
        """, (pid,))

        goals = cur.fetchall()

        # v2.0: Load focus_contributions for each goal
        goal_ids = [g['id'] for g in goals]
        focus_map = {}  # goal_id → [contributions]

        if goal_ids:
            try:
                placeholders = ','.join(['%s'] * len(goal_ids))
                cur.execute(f"""
                    SELECT
                        gfc.goal_id, gfc.contribution_weight,
                        fa.id as focus_area_id, fa.key, fa.name_de, fa.icon, fa.category
                    FROM goal_focus_contributions gfc
                    JOIN focus_area_definitions fa ON gfc.focus_area_id = fa.id
                    WHERE gfc.goal_id IN ({placeholders})
                    ORDER BY gfc.contribution_weight DESC
                """, tuple(goal_ids))

                for row in cur.fetchall():
                    gid = row['goal_id']
                    if gid not in focus_map:
                        focus_map[gid] = []
                    focus_map[gid].append({
                        'focus_area_id': row['focus_area_id'],
                        'key': row['key'],
                        'name_de': row['name_de'],
                        'icon': row['icon'],
                        'category': row['category'],
                        'contribution_weight': float(row['contribution_weight'])
                    })
            except Exception as e:
                # Migration 031 not yet applied - focus_contributions tables don't exist
                print(f"[WARNING] Could not load focus_contributions: {e}")
                # Continue without focus_contributions (backward compatible)

        # Group by category and attach focus_contributions
        grouped = {}
        for goal in goals:
            cat = goal['category'] or 'other'
            if cat not in grouped:
                grouped[cat] = []

            goal_dict = r2d(goal)
            goal_dict['focus_contributions'] = focus_map.get(goal['id'], [])
            grouped[cat].append(goal_dict)

        # Serialize date objects to ISO format strings
        grouped = serialize_dates(grouped)

        return grouped

# ============================================================================
# Helper Functions
# ============================================================================

def _get_current_value_for_goal_type(conn, profile_id: str, goal_type: str) -> Optional[float]:
    """
    Get current value for a goal type.

    DEPRECATED: This function now delegates to the universal fetcher in goal_utils.py.
    Phase 1.5: All goal types are now defined in goal_type_definitions table.

    Args:
        conn: Database connection
        profile_id: User's profile ID
        goal_type: Goal type key (e.g., 'weight', 'meditation_minutes')

    Returns:
        Current value or None
    """
    # Delegate to universal fetcher (Phase 1.5)
    return get_current_value_for_goal(conn, profile_id, goal_type)

def _get_historical_value_for_goal_type(conn, profile_id: str, goal_type: str, target_date: date) -> Optional[dict]:
    """
    Get historical value for a goal type on or after a specific date.
    Finds the FIRST available measurement >= target_date.

    Args:
        conn: Database connection
        profile_id: User's profile ID
        goal_type: Goal type key (e.g., 'weight', 'body_fat')
        target_date: Desired start date (will find first measurement on or after this date)

    Returns:
        Dict with {'value': float, 'date': date} or None if not found
    """
    from goal_utils import get_goal_type_config, get_cursor

    # Get goal type configuration
    config = get_goal_type_config(conn, goal_type)
    if not config:
        return None

    source_table = config.get('source_table')
    source_column = config.get('source_column')


    if not source_table or not source_column:
        return None

    # Query for value closest to target_date (±7 days window)
    cur = get_cursor(conn)

    try:
        # Special handling for different tables
        if source_table == 'vitals_baseline':
            date_col = 'date'
        elif source_table == 'blood_pressure_log':
            date_col = 'recorded_at::date'
        else:
            date_col = 'date'

        # Find first measurement on or after target_date
        query = f"""
            SELECT {source_column}, {date_col} as measurement_date
            FROM {source_table}
            WHERE profile_id = %s
              AND {date_col} >= %s
            ORDER BY {date_col} ASC
            LIMIT 1
        """
        params = (profile_id, target_date)


        cur.execute(query, params)

        row = cur.fetchone()

        if row:
            value = row[source_column]
            measurement_date = row['measurement_date']

            # Convert Decimal to float
            result_value = float(value) if value is not None else None

            # Handle different date types (date vs datetime)
            if hasattr(measurement_date, 'date'):
                # It's a datetime, extract date
                result_date = measurement_date.date()
            else:
                # It's already a date
                result_date = measurement_date

            result = {'value': result_value, 'date': result_date}
            return result

        return None
    except Exception as e:
        print(f"[ERROR] Failed to get historical value for {goal_type} on {target_date}: {e}")
        return None

def _update_goal_progress(conn, profile_id: str, goal: dict):
    """Update goal progress (modifies goal dict in-place)"""
    # Get current value
    current = _get_current_value_for_goal_type(conn, profile_id, goal['goal_type'])

    if current is not None and goal['start_value'] is not None and goal['target_value'] is not None:
        goal['current_value'] = current

        # Calculate progress percentage
        total_delta = float(goal['target_value']) - float(goal['start_value'])
        current_delta = current - float(goal['start_value'])

        if total_delta != 0:
            progress_pct = (current_delta / total_delta) * 100
            goal['progress_pct'] = round(progress_pct, 2)

            # Simple linear projection
            if goal['start_date'] and current_delta != 0:
                days_elapsed = (date.today() - goal['start_date']).days
                if days_elapsed > 0:
                    days_per_unit = days_elapsed / current_delta
                    remaining_units = float(goal['target_value']) - current
                    remaining_days = int(days_per_unit * remaining_units)
                    goal['projection_date'] = date.today() + timedelta(days=remaining_days)

                    # Check if on track
                    if goal['target_date'] and goal['projection_date']:
                        goal['on_track'] = goal['projection_date'] <= goal['target_date']
