#!/usr/bin/env python3
"""Daily Whoop pull (v2 API). Refreshes access token, pulls latest cycle/recovery/sleep, writes readiness file."""
import json, sys
from datetime import datetime, timezone
from pathlib import Path

import requests

LIFE = Path("/data/cameron/life")
CREDS = LIFE / ".whoop_creds.json"
TOKENS = LIFE / ".whoop_tokens.json"
READINESS = LIFE / ".whoop_readiness.json"
LOG = LIFE / ".whoop_pull.log"

TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
API_BASE = "https://api.prod.whoop.com/developer/v2"


def log(msg):
    ts = datetime.now(timezone.utc).isoformat()
    with LOG.open("a") as f:
        f.write(f"{ts} {msg}\n")


def refresh_access_token(creds, tokens):
    r = requests.post(
        TOKEN_URL,
        data={
            "grant_type": "refresh_token",
            "refresh_token": tokens["refresh_token"],
            "client_id": creds["client_id"],
            "client_secret": creds["client_secret"],
            "scope": "offline",
        },
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        timeout=30,
    )
    r.raise_for_status()
    new_tokens = r.json()
    TOKENS.write_text(json.dumps(new_tokens, indent=2))
    TOKENS.chmod(0o600)
    return new_tokens


def get(access_token, path):
    r = requests.get(
        f"{API_BASE}{path}",
        headers={"Authorization": f"Bearer {access_token}"},
        timeout=30,
    )
    r.raise_for_status()
    return r.json()


def latest(records_payload):
    recs = records_payload.get("records") or []
    return recs[0] if recs else {}


def main():
    try:
        creds = json.loads(CREDS.read_text())
        tokens = json.loads(TOKENS.read_text())

        tokens = refresh_access_token(creds, tokens)
        access_token = tokens["access_token"]

        cycle = latest(get(access_token, "/cycle?limit=1"))
        recovery = latest(get(access_token, "/recovery?limit=1"))
        sleep = latest(get(access_token, "/activity/sleep?limit=1"))
        try:
            workouts_payload = get(access_token, "/activity/workout?limit=5")
            workouts = workouts_payload.get("records") or []
        except Exception as e:
            workouts = []
            log(f"workouts fetch failed (likely scope): {e}")

        rec_score = (recovery.get("score") or {})
        sleep_score = (sleep.get("score") or {})
        sleep_stages = (sleep_score.get("stage_summary") or {})

        in_bed_ms = sleep_stages.get("total_in_bed_time_milli", 0)
        awake_ms = sleep_stages.get("total_awake_time_milli", 0)
        asleep_hrs = (in_bed_ms - awake_ms) / 3600000 if in_bed_ms else None

        cycle_start = cycle.get("start")
        workouts_today = []
        if cycle_start:
            for w in workouts:
                if w.get("start", "") >= cycle_start:
                    workouts_today.append({
                        "sport_id": w.get("sport_id"),
                        "start": w.get("start"),
                        "end": w.get("end"),
                        "in_progress": w.get("end") is None,
                        "strain": (w.get("score") or {}).get("strain"),
                        "kilojoule": (w.get("score") or {}).get("kilojoule"),
                        "average_heart_rate": (w.get("score") or {}).get("average_heart_rate"),
                        "max_heart_rate": (w.get("score") or {}).get("max_heart_rate"),
                    })

        summary = {
            "recovery_score": rec_score.get("recovery_score"),
            "hrv_rmssd_milli": rec_score.get("hrv_rmssd_milli"),
            "resting_heart_rate": rec_score.get("resting_heart_rate"),
            "spo2_percentage": rec_score.get("spo2_percentage"),
            "skin_temp_celsius": rec_score.get("skin_temp_celsius"),
            "sleep_performance_percentage": sleep_score.get("sleep_performance_percentage"),
            "sleep_efficiency_percentage": sleep_score.get("sleep_efficiency_percentage"),
            "sleep_consistency_percentage": sleep_score.get("sleep_consistency_percentage"),
            "respiratory_rate": sleep_score.get("respiratory_rate"),
            "hours_asleep": round(asleep_hrs, 2) if asleep_hrs is not None else None,
            "disturbance_count": sleep_stages.get("disturbance_count"),
            "rem_minutes": round(sleep_stages.get("total_rem_sleep_time_milli", 0) / 60000) or None,
            "deep_minutes": round(sleep_stages.get("total_slow_wave_sleep_time_milli", 0) / 60000) or None,
            "strain_today": (cycle.get("score") or {}).get("strain"),
            "kilojoules_today": (cycle.get("score") or {}).get("kilojoule"),
            "workouts_today": workouts_today,
            "workout_count_today": len(workouts_today),
            "current_workout_in_progress": any(w["in_progress"] for w in workouts_today),
        }

        result = {
            "pulled_at": datetime.now(timezone.utc).isoformat(),
            "summary": summary,
            "cycle": cycle,
            "recovery": recovery,
            "sleep": sleep,
            "workouts": workouts,
        }
        READINESS.write_text(json.dumps(result, indent=2))
        READINESS.chmod(0o600)

        log(
            f"OK recovery={summary['recovery_score']} "
            f"hrv={summary['hrv_rmssd_milli']} "
            f"rhr={summary['resting_heart_rate']} "
            f"sleep_hrs={summary['hours_asleep']} "
            f"sleep_perf={summary['sleep_performance_percentage']}"
        )
        print(json.dumps(summary, indent=2))

    except Exception as e:
        log(f"ERROR {type(e).__name__}: {e}")
        print(f"ERROR: {e}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
