from datetime import date, timedelta

from fastapi import APIRouter, Depends
from sqlalchemy import select, func, extract, cast, Date
from sqlalchemy.ext.asyncio import AsyncSession

from backend.app.core.auth import get_current_rider
from backend.app.core.database import get_session
from backend.app.models.activity import Activity, ActivityMetrics
from backend.app.models.fitness import PowerCurve
from backend.app.models.rider import Rider
from backend.app.schemas.rider import RiderUpdate, RiderResponse, FitnessHistoryResponse
from backend.app.services.fitness import get_fitness_history

router = APIRouter()


@router.get("/profile", response_model=RiderResponse)
async def get_rider(rider: Rider = Depends(get_current_rider)):
    return rider


@router.put("/profile", response_model=RiderResponse)
async def update_rider(
    data: RiderUpdate,
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    update_data = data.model_dump(exclude_unset=True)
    ftp_changed = "ftp" in update_data and update_data["ftp"] != rider.ftp

    for key, value in update_data.items():
        setattr(rider, key, value)

    # Recalculate TSS/IF for all activities when FTP changes
    if ftp_changed and rider.ftp:
        query = (
            select(ActivityMetrics)
            .join(Activity, Activity.id == ActivityMetrics.activity_id)
            .where(Activity.rider_id == rider.id)
            .where(ActivityMetrics.normalized_power.isnot(None))
        )
        result = await session.execute(query)
        for metrics in result.scalars().all():
            np_val = metrics.normalized_power
            duration_q = await session.execute(
                select(Activity.duration).where(Activity.id == metrics.activity_id)
            )
            duration = duration_q.scalar()
            if np_val and duration:
                metrics.intensity_factor = round(np_val / rider.ftp, 2)
                metrics.tss = round(
                    (duration * np_val * (np_val / rider.ftp))
                    / (rider.ftp * 3600)
                    * 100,
                    1,
                )

    await session.commit()
    await session.refresh(rider)
    return rider


@router.get("/fitness", response_model=list[FitnessHistoryResponse])
async def get_fitness(
    days: int = 90,
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    entries = await get_fitness_history(rider.id, session, days=days)
    return entries


@router.get("/weekly-stats")
async def get_weekly_stats(
    weeks: int = 8,
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    today = date.today()
    # Start from Monday of current week
    start_of_week = today - timedelta(days=today.weekday())
    start_date = start_of_week - timedelta(weeks=weeks - 1)

    week_col = func.date_trunc('week', Activity.date).label("week")

    query = (
        select(
            week_col,
            func.count(Activity.id).label("rides"),
            func.sum(Activity.duration).label("duration"),
            func.sum(Activity.distance).label("distance"),
            func.sum(ActivityMetrics.tss).label("tss"),
        )
        .select_from(Activity)
        .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id)
        .where(Activity.rider_id == rider.id)
        .where(Activity.date >= start_date)
        .group_by(week_col)
        .order_by(week_col)
    )

    result = await session.execute(query)

    return [
        {
            "week": row.week.strftime("%Y-%m-%d") if row.week else None,
            "rides": row.rides,
            "duration": row.duration or 0,
            "distance": round(float(row.distance or 0) / 1000, 1),
            "tss": round(float(row.tss or 0), 0),
        }
        for row in result
    ]


@router.get("/personal-records")
async def get_personal_records(
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """Best power at each standard duration across all activities."""
    query = (
        select(PowerCurve.curve_data, Activity.date, Activity.name, Activity.id)
        .join(Activity, Activity.id == PowerCurve.activity_id)
        .where(Activity.rider_id == rider.id)
    )
    result = await session.execute(query)

    # Merge all curves: keep best power + source activity for each duration
    records: dict[int, dict] = {}
    for row in result:
        curve_data = row.curve_data
        for dur_str, power in curve_data.items():
            dur = int(dur_str)
            if dur not in records or power > records[dur]["power"]:
                records[dur] = {
                    "duration": dur,
                    "power": power,
                    "activity_id": str(row.id),
                    "activity_name": row.name or "Ride",
                    "date": row.date.isoformat(),
                }

    # Sort by duration
    return sorted(records.values(), key=lambda r: r["duration"])


@router.get("/detect-ftp")
async def detect_ftp(
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """Auto-detect FTP from best 20min power × 0.95 across all activities."""
    query = (
        select(PowerCurve.curve_data, Activity.date, Activity.name, Activity.id)
        .join(Activity, Activity.id == PowerCurve.activity_id)
        .where(Activity.rider_id == rider.id)
    )
    result = await session.execute(query)

    best_20min = 0
    best_activity_id = None
    best_activity_name = None
    best_date = None

    for row in result:
        power_20 = row.curve_data.get("1200")  # 20 min = 1200s
        if power_20 and power_20 > best_20min:
            best_20min = power_20
            best_activity_id = str(row.id)
            best_activity_name = row.name or "Ride"
            best_date = row.date.isoformat()

    if best_20min == 0:
        return {"detected_ftp": None, "best_20min_power": None, "message": "No 20-minute power data found"}

    detected_ftp = round(best_20min * 0.95)
    return {
        "detected_ftp": detected_ftp,
        "best_20min_power": best_20min,
        "activity_id": best_activity_id,
        "activity_name": best_activity_name,
        "date": best_date,
        "current_ftp": rider.ftp,
    }


@router.get("/activity-calendar")
async def get_activity_calendar(
    months: int = 12,
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """Activity heatmap data: date → TSS for the last N months."""
    start_date = date.today() - timedelta(days=months * 30)
    day_col = cast(Activity.date, Date).label("day")

    query = (
        select(
            day_col,
            func.count(Activity.id).label("count"),
            func.sum(Activity.duration).label("duration"),
            func.sum(ActivityMetrics.tss).label("tss"),
        )
        .select_from(Activity)
        .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id)
        .where(Activity.rider_id == rider.id)
        .where(Activity.date >= start_date)
        .group_by(day_col)
        .order_by(day_col)
    )
    result = await session.execute(query)

    return [
        {
            "date": row.day.isoformat(),
            "count": row.count,
            "duration": row.duration or 0,
            "tss": round(float(row.tss or 0), 0),
        }
        for row in result
    ]


@router.get("/monthly-trends")
async def get_monthly_trends(
    months: int = 12,
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """Monthly trends: avg power, avg HR, volume, W/kg."""
    start_date = date.today() - timedelta(days=months * 30)
    month_col = func.date_trunc("month", Activity.date).label("month")

    query = (
        select(
            month_col,
            func.count(Activity.id).label("rides"),
            func.sum(Activity.duration).label("duration"),
            func.sum(Activity.distance).label("distance"),
            func.avg(ActivityMetrics.avg_power).label("avg_power"),
            func.avg(ActivityMetrics.normalized_power).label("avg_np"),
            func.avg(ActivityMetrics.avg_hr).label("avg_hr"),
            func.sum(ActivityMetrics.tss).label("tss"),
        )
        .select_from(Activity)
        .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id)
        .where(Activity.rider_id == rider.id)
        .where(Activity.date >= start_date)
        .group_by(month_col)
        .order_by(month_col)
    )
    result = await session.execute(query)

    weight = rider.weight
    return [
        {
            "month": row.month.strftime("%Y-%m") if row.month else None,
            "rides": row.rides,
            "hours": round((row.duration or 0) / 3600, 1),
            "distance_km": round(float(row.distance or 0) / 1000, 0),
            "avg_power": round(float(row.avg_power), 0) if row.avg_power else None,
            "avg_np": round(float(row.avg_np), 0) if row.avg_np else None,
            "avg_hr": round(float(row.avg_hr), 0) if row.avg_hr else None,
            "tss": round(float(row.tss or 0), 0),
            "w_per_kg": round(float(row.avg_power) / weight, 2) if row.avg_power and weight else None,
        }
        for row in result
    ]


@router.get("/progress-summary")
async def get_progress_summary(
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """Compare this week vs last week, this month vs last month. Current fitness state."""
    today = date.today()
    start_of_week = today - timedelta(days=today.weekday())  # Monday
    last_week_start = start_of_week - timedelta(weeks=1)

    start_of_month = today.replace(day=1)
    last_month_start = (start_of_month - timedelta(days=1)).replace(day=1)

    async def _period_stats(start: date, end: date):
        query = (
            select(
                func.count(Activity.id).label("rides"),
                func.sum(Activity.duration).label("duration"),
                func.sum(Activity.distance).label("distance"),
                func.sum(ActivityMetrics.tss).label("tss"),
                func.avg(ActivityMetrics.avg_power).label("avg_power"),
                func.avg(ActivityMetrics.normalized_power).label("avg_np"),
                func.avg(ActivityMetrics.avg_hr).label("avg_hr"),
            )
            .select_from(Activity)
            .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id)
            .where(Activity.rider_id == rider.id)
            .where(Activity.date >= start)
            .where(Activity.date < end)
        )
        row = (await session.execute(query)).first()
        if not row or row.rides == 0:
            return {"rides": 0, "hours": 0, "distance_km": 0, "tss": 0,
                    "avg_power": None, "avg_np": None, "avg_hr": None}
        return {
            "rides": row.rides,
            "hours": round((row.duration or 0) / 3600, 1),
            "distance_km": round(float(row.distance or 0) / 1000, 1),
            "tss": round(float(row.tss or 0), 0),
            "avg_power": round(float(row.avg_power), 0) if row.avg_power else None,
            "avg_np": round(float(row.avg_np), 0) if row.avg_np else None,
            "avg_hr": round(float(row.avg_hr), 0) if row.avg_hr else None,
        }

    this_week = await _period_stats(start_of_week, today + timedelta(days=1))
    last_week = await _period_stats(last_week_start, start_of_week)
    this_month = await _period_stats(start_of_month, today + timedelta(days=1))
    last_month = await _period_stats(last_month_start, start_of_month)

    # Current fitness (latest CTL/ATL/TSB)
    from backend.app.models.fitness import FitnessHistory
    fh_query = (
        select(FitnessHistory)
        .where(FitnessHistory.rider_id == rider.id)
        .order_by(FitnessHistory.date.desc())
        .limit(1)
    )
    fh = (await session.execute(fh_query)).scalar_one_or_none()

    # Fitness 7 days ago for trend
    fh_prev_query = (
        select(FitnessHistory)
        .where(FitnessHistory.rider_id == rider.id)
        .where(FitnessHistory.date <= today - timedelta(days=7))
        .order_by(FitnessHistory.date.desc())
        .limit(1)
    )
    fh_prev = (await session.execute(fh_prev_query)).scalar_one_or_none()

    fitness_now = {
        "ctl": round(fh.ctl, 1) if fh else None,
        "atl": round(fh.atl, 1) if fh else None,
        "tsb": round(fh.tsb, 1) if fh else None,
    }
    fitness_prev = {
        "ctl": round(fh_prev.ctl, 1) if fh_prev else None,
        "atl": round(fh_prev.atl, 1) if fh_prev else None,
        "tsb": round(fh_prev.tsb, 1) if fh_prev else None,
    }

    # Total activity count
    total_count = (await session.execute(
        select(func.count(Activity.id)).where(Activity.rider_id == rider.id)
    )).scalar() or 0

    # Days since last activity
    last_act = (await session.execute(
        select(Activity.date)
        .where(Activity.rider_id == rider.id)
        .order_by(Activity.date.desc())
        .limit(1)
    )).scalar_one_or_none()

    if last_act is not None:
        from datetime import datetime as dt_type
        last_date = last_act.date() if isinstance(last_act, dt_type) else last_act
        days_since_last = (today - last_date).days
    else:
        days_since_last = None

    return {
        "this_week": this_week,
        "last_week": last_week,
        "this_month": this_month,
        "last_month": last_month,
        "fitness": fitness_now,
        "fitness_7d_ago": fitness_prev,
        "total_activities": total_count,
        "days_since_last_activity": days_since_last,
        "ftp": rider.ftp,
        "weight": rider.weight,
    }


@router.get("/ai-progress")
async def get_ai_progress(
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """AI analysis of long-term progress with actionable recommendations."""
    from backend.app.services.coaching import build_rider_context
    from backend.app.services.gemini_client import chat_async
    from backend.app.models.fitness import FitnessHistory

    today = date.today()

    rider_context = await build_rider_context(rider, session)

    # Collect monthly stats for the last 6 months
    lines = [rider_context, "\n--- Monthly breakdown (last 6 months) ---"]
    for i in range(6):
        m_start = (today.replace(day=1) - timedelta(days=30 * i)).replace(day=1)
        m_end = (m_start + timedelta(days=32)).replace(day=1)
        query = (
            select(
                func.count(Activity.id).label("rides"),
                func.sum(Activity.duration).label("duration"),
                func.sum(Activity.distance).label("distance"),
                func.sum(ActivityMetrics.tss).label("tss"),
                func.avg(ActivityMetrics.avg_power).label("avg_power"),
                func.avg(ActivityMetrics.avg_hr).label("avg_hr"),
            )
            .select_from(Activity)
            .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id)
            .where(Activity.rider_id == rider.id)
            .where(Activity.date >= m_start)
            .where(Activity.date < m_end)
        )
        row = (await session.execute(query)).first()
        if row and row.rides:
            lines.append(
                f"{m_start.strftime('%Y-%m')}: {row.rides} rides, "
                f"{(row.duration or 0) / 3600:.1f}h, "
                f"TSS={float(row.tss or 0):.0f}, "
                f"AP={float(row.avg_power):.0f}W" if row.avg_power else f"{m_start.strftime('%Y-%m')}: {row.rides} rides"
            )

    # Fitness trend: sample every 7 days for last 90 days
    fh_query = (
        select(FitnessHistory.date, FitnessHistory.ctl, FitnessHistory.atl, FitnessHistory.tsb)
        .where(FitnessHistory.rider_id == rider.id)
        .where(FitnessHistory.date >= today - timedelta(days=90))
        .order_by(FitnessHistory.date)
    )
    fh_rows = (await session.execute(fh_query)).all()
    if fh_rows:
        lines.append("\n--- Fitness trend (sampled weekly, last 90d) ---")
        for idx, fh in enumerate(fh_rows):
            if idx % 7 == 0 or idx == len(fh_rows) - 1:
                lines.append(f"{fh.date}: CTL={fh.ctl:.0f} ATL={fh.atl:.0f} TSB={fh.tsb:.0f}")

    system = """You are VeloBrain AI Coach performing a comprehensive progress analysis.

Analyze the rider's data and produce a structured report with these sections:

1. **Текущее состояние** — Current fitness level, form, fatigue. Is the rider fresh, building, overreaching?
2. **Динамика прогресса** — How fitness, power, and volume have changed over the last months. Specific numbers and trends.
3. **Сильные стороны** — What the rider is doing well (consistency, intensity, volume, specific power durations).
4. **Зоны роста** — Specific weaknesses or areas for improvement. Be constructive.
5. **Рекомендации** — 3-5 specific, actionable recommendations for the next 4 weeks. Include target numbers where possible.
6. **Риски** — Any warning signs: overtraining, stagnation, imbalance. Only include if relevant.

Be specific with numbers. Compare periods where possible.
Keep each section 2-4 sentences.
Respond in Russian. Use HTML formatting."""

    analysis = await chat_async(
        [{"role": "user", "text": "\n".join(lines)}],
        system_instruction=system,
        temperature=0.5,
    )

    return {"analysis": analysis}


@router.get("/weekly-digest")
async def get_weekly_digest(
    rider: Rider = Depends(get_current_rider),
    session: AsyncSession = Depends(get_session),
):
    """Get data for AI weekly digest generation."""
    from backend.app.services.coaching import build_rider_context
    from backend.app.services.gemini_client import chat_async

    today = date.today()
    week_start = today - timedelta(days=today.weekday() + 7)  # Last Monday
    week_end = week_start + timedelta(days=7)

    # Activities from last week
    act_query = (
        select(Activity, ActivityMetrics)
        .outerjoin(ActivityMetrics, ActivityMetrics.activity_id == Activity.id)
        .where(Activity.rider_id == rider.id)
        .where(Activity.date >= week_start)
        .where(Activity.date < week_end)
        .order_by(Activity.date)
    )
    result = await session.execute(act_query)
    activities = list(result.all())

    if not activities:
        return {"digest": None, "message": "No activities last week"}

    # Build prompt
    lines = [f"Weekly digest for {week_start.isoformat()} — {week_end.isoformat()}"]
    total_duration = 0
    total_tss = 0
    total_distance = 0

    for act, metrics in activities:
        total_duration += act.duration
        total_distance += act.distance or 0
        tss = float(metrics.tss or 0) if metrics else 0
        total_tss += tss
        lines.append(
            f"- {act.date.strftime('%a')}: {act.name or 'Workout'}, "
            f"{act.duration // 60}min, "
            f"{f'{act.distance / 1000:.1f}km, ' if act.distance else ''}"
            f"{f'AP={metrics.avg_power:.0f}W, NP={metrics.normalized_power:.0f}W, ' if metrics and metrics.avg_power else ''}"
            f"TSS={tss:.0f}"
        )

    lines.append(f"\nTotals: {len(activities)} workouts, {total_duration / 3600:.1f}h, "
                 f"{total_distance / 1000:.1f}km, TSS={total_tss:.0f}")

    rider_context = await build_rider_context(rider, session)
    lines.append(f"\n--- Rider ---\n{rider_context}")

    system = """You are VeloBrain AI Coach generating a weekly training digest.
Provide a concise summary with these sections:
1. **Итоги недели** — what was done, key numbers
2. **Что получилось хорошо** — highlights, PRs, consistency
3. **На что обратить внимание** — areas for improvement, recovery needs
4. **Рекомендации на следующую неделю** — specific actionable advice

Keep it 8-12 sentences total. Be specific with numbers.
Respond in Russian. Use HTML formatting."""

    digest = await chat_async(
        [{"role": "user", "text": "\n".join(lines)}],
        system_instruction=system,
        temperature=0.5,
    )

    return {
        "digest": digest,
        "week_start": week_start.isoformat(),
        "week_end": week_end.isoformat(),
        "total_activities": len(activities),
        "total_hours": round(total_duration / 3600, 1),
        "total_tss": round(total_tss, 0),
        "total_distance_km": round(total_distance / 1000, 1),
    }
