"""Activity detail — drill-in for a single run.

Accessed via `?aid=<activity_id>`. Layout:
    Header: tiles (date, distance, pace, avg HR, training load, vo2_max)
    Section: splits table + pace-vs-HR scatter
    Section: HR time-in-zone bars (FIT or split-based, whichever the cache has)
    Section: FIT decoupling chart (if a FIT is linked)
    Section: map (lat/long from FIT records, if present)
"""

from __future__ import annotations

import matplotlib.pyplot as plt
import pandas as pd
import streamlit as st

from openrun import (
    HRZones,
    default_profile,
    load_fit_records,
    load_splits,
    time_in_zone_from_fit,
    time_in_zone_from_splits,
)
from openrun.plots import plot_fit_decoupling
from openrun.web._helpers import cached_activities, empty_state, get_conn, show_sidebar


st.set_page_config(page_title="Activity · openrun", page_icon="🏃", layout="wide")
show_sidebar()


# ---------------------------------------------------------------------------
# Resolve activity from query param
# ---------------------------------------------------------------------------

aid_str = st.query_params.get("aid")
if not aid_str:
    st.title("🏃 Activity detail")
    empty_state("No activity selected.", "Open this page from the Activities list.")
    if st.button("← Activities"):
        st.switch_page("pages/2_Activities.py")
    st.stop()

try:
    aid = int(aid_str)
except ValueError:
    st.error(f"Invalid activity id: {aid_str!r}")
    st.stop()

runs = cached_activities(type=None)
match = runs[runs["activity_id"] == aid]
if match.empty:
    st.error(f"No activity {aid} in the DB.")
    st.stop()
row = match.iloc[0]


# ---------------------------------------------------------------------------
# Header
# ---------------------------------------------------------------------------

c_back, c_title = st.columns([1, 8])
if c_back.button("← Activities"):
    st.query_params.clear()
    st.switch_page("pages/2_Activities.py")
c_title.title(f"🏃 {row['activity_name'] or 'Activity'}")
st.caption(
    f"{row['start_time_local']:%Y-%m-%d %H:%M}  ·  {row['activity_type']}  ·  id={aid}"
)


def _fmt_pace(p: float) -> str:
    if pd.isna(p):
        return "—"
    return f"{int(p)}:{int(round((p - int(p)) * 60)):02d}/km"


def _fmt_duration(s: float) -> str:
    if pd.isna(s):
        return "—"
    s = int(s)
    h, rem = divmod(s, 3600)
    m, sec = divmod(rem, 60)
    return f"{h}h {m:02d}m" if h else f"{m}m {sec:02d}s"


cols = st.columns(6)
cols[0].metric("Distance", f"{row['distance_km']:.2f} km" if pd.notna(row["distance_km"]) else "—")
cols[1].metric("Duration", _fmt_duration(row.get("moving_duration_s", row.get("duration_s"))))
cols[2].metric("Pace", _fmt_pace(row.get("pace_min_per_km")))
cols[3].metric("Avg HR", f"{int(row['avg_hr'])}" if pd.notna(row["avg_hr"]) else "—")
cols[4].metric("Training load", f"{int(row['training_load'])}" if pd.notna(row["training_load"]) else "—")
cols[5].metric("VO₂ max", f"{row['vo2_max']:.1f}" if pd.notna(row.get("vo2_max")) else "—")


# ---------------------------------------------------------------------------
# Splits
# ---------------------------------------------------------------------------

splits = load_splits(get_conn(), activity_type=None)
my_splits = splits[splits["activity_id"] == aid].sort_values("split_seq")

if not my_splits.empty:
    st.subheader(f"Splits ({len(my_splits)})")
    s_show = my_splits[[
        "split_index", "distance_m", "duration_s", "pace_min_per_km",
        "avg_hr", "split_elev_gain_m",
    ]].copy()
    s_show["distance_km"] = (s_show["distance_m"] / 1000).round(2)
    s_show["pace"] = s_show["pace_min_per_km"].apply(_fmt_pace)
    s_show["duration"] = s_show["duration_s"].apply(_fmt_duration)
    s_show = s_show.rename(columns={
        "split_index": "#",
        "avg_hr": "Avg HR",
        "split_elev_gain_m": "Elev↑ (m)",
    })[["#", "distance_km", "duration", "pace", "Avg HR", "Elev↑ (m)"]].rename(
        columns={"distance_km": "Distance (km)", "duration": "Duration", "pace": "Pace"}
    )
    st.dataframe(s_show, hide_index=True, width="stretch", height=260)

    # Pace vs HR scatter per split — useful for spotting drift across the run.
    if my_splits["pace_min_per_km"].notna().any() and my_splits["avg_hr"].notna().any():
        fig, ax = plt.subplots(figsize=(8, 3))
        ax.scatter(my_splits["pace_min_per_km"], my_splits["avg_hr"],
                   c=my_splits["split_seq"], cmap="viridis", s=42)
        ax.set_xlabel("pace (min/km)")
        ax.set_ylabel("avg HR (bpm)")
        ax.invert_xaxis()  # faster pace on right
        ax.set_title("Splits: pace vs HR (colour = order)")
        fig.tight_layout()
        st.pyplot(fig, clear_figure=True)


# ---------------------------------------------------------------------------
# Time in zone
# ---------------------------------------------------------------------------

st.subheader("Time in HR zone")
tiz_row = get_conn().execute(
    "SELECT z1_s, z2_s, z3_s, z4_s, z5_s, source FROM activity_time_in_zone WHERE activity_id=?",
    (aid,),
).fetchone()

tiz: dict[str, float] | None = None
source = None
if tiz_row is not None:
    tiz = {f"Z{i}": tiz_row[f"z{i}_s"] for i in range(1, 6)}
    source = tiz_row["source"]
elif not my_splits.empty:
    tiz = time_in_zone_from_splits(my_splits)
    source = "lap"

if tiz:
    z_series = pd.Series(tiz, dtype=float).reindex(["Z1", "Z2", "Z3", "Z4", "Z5"]).fillna(0)
    total = float(z_series.sum())
    if total > 0:
        fig, ax = plt.subplots(figsize=(8, 2.4))
        colors = ["#2a9d8f", "#a4d4ae", "#e9c46a", "#f4a261", "#e76f51"]
        ax.bar(z_series.index, z_series / 60, color=colors)
        ax.set_ylabel("minutes")
        for i, v in enumerate(z_series / 60):
            ax.text(i, v, f"{v:.0f}m\n({v*60/total*100:.0f}%)", ha="center", va="bottom", fontsize=8)
        ax.set_ylim(top=(z_series / 60).max() * 1.25)
        fig.tight_layout()
        st.pyplot(fig, clear_figure=True)
        st.caption(f"Source: **{source}** (`fit` = per-second, `lap` = split-average bias).")
else:
    st.info("No TIZ data — run `openrun-time-in-zone` to populate the cache.")


# ---------------------------------------------------------------------------
# FIT decoupling (if linked)
# ---------------------------------------------------------------------------

fit_linked = get_conn().execute(
    "SELECT fit_path FROM activity_fit_files WHERE activity_id=?", (aid,)
).fetchone()

if fit_linked:
    st.subheader("Pa:Hr decoupling (per-second from FIT)")
    segments = st.select_slider("Segments", options=[2, 3, 4, 6], value=4)
    try:
        records = load_fit_records(get_conn(), aid)
        if records.empty:
            st.info("FIT linked but has no per-second records.")
        else:
            ax = plot_fit_decoupling(records, segments=segments)
            st.pyplot(ax.figure, clear_figure=True)

            # Cadence trace — training cue is ~170 spm, natural fallback ~161
            # (see NatesNotes.md). 30 s rolling mean to cut sensor noise.
            cad = records.dropna(subset=["cadence_spm"])
            cad = cad[cad["cadence_spm"] > 0]  # drop standing/paused samples
            if not cad.empty:
                st.subheader("Cadence (per-second from FIT)")
                smooth = (
                    cad.set_index("elapsed_s")["cadence_spm"]
                    .rolling(30, min_periods=5, center=True).mean()
                )
                fig, axc = plt.subplots(figsize=(8, 2.4))
                axc.plot(smooth.index / 60, smooth.values, color="#264653", lw=1.2)
                axc.axhline(170, color="#2a9d8f", ls="--", lw=1, label="target 170")
                axc.axhline(161, color="#e76f51", ls=":", lw=1, label="natural 161")
                axc.set_xlabel("elapsed (min)")
                axc.set_ylabel("spm")
                axc.legend(fontsize=8, loc="lower right")
                for s in ("top", "right"):
                    axc.spines[s].set_visible(False)
                fig.tight_layout()
                st.pyplot(fig, clear_figure=True)
                st.caption(
                    f"Moving average cadence: **{cad['cadence_spm'].mean():.0f} spm** · "
                    f"time ≥170: **{(cad['cadence_spm'] >= 170).mean() * 100:.0f}%**"
                )
    except FileNotFoundError as exc:
        st.error(str(exc))
else:
    st.caption("No FIT file linked. Run `openrun-link-fit <export>` to enable decoupling charts.")
