#!/usr/bin/env python3
"""Import daily step/calorie totals from Google Fit REST into Health Bridge.

Requires:
  GOOGLE_FIT_ACCESS_TOKEN with scope https://www.googleapis.com/auth/fitness.activity.read

This queries Google's app-style estimated_steps source:
  derived:com.google.step_count.delta:com.google.android.gms:estimated_steps
and posts daily_aggregate records into Health Bridge.
"""
import json
import os
import sys
import urllib.error
import urllib.request
from datetime import datetime, date, time, timedelta
from zoneinfo import ZoneInfo

FIT_URL = "https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate"
INGEST_URL = os.environ.get("HEALTH_BRIDGE_INGEST_URL", "http://100.87.116.90:3007/ingest")
HEALTH_TOKEN = os.environ.get("HEALTH_BRIDGE_TOKEN", "XXMYWq-ft146fRL0AlNV_5P9Vg4NNJJa4pHxqDvzRkg")
SUBJECT = os.environ.get("HEALTH_BRIDGE_SUBJECT", "chicho")
TZ = os.environ.get("HEALTH_BRIDGE_TIMEZONE", "America/Argentina/Buenos_Aires")
SOURCE = "google_fit_rest"

def millis(d: date, zone: ZoneInfo) -> int:
    return int(datetime.combine(d, time.min, tzinfo=zone).timestamp() * 1000)

def request_json(url, body=None, headers=None):
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(url, data=data, headers=headers or {}, method="POST" if body is not None else "GET")
    try:
        with urllib.request.urlopen(req, timeout=60) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        detail = e.read().decode()
        raise RuntimeError(f"HTTP {e.code} from {url}: {detail}") from e

def extract_metrics(bucket):
    metrics = {"steps": 0, "total_kcal": 0.0}
    for dataset in bucket.get("dataset", []):
        dtype = dataset.get("dataSourceId", "")
        for point in dataset.get("point", []):
            ptype = point.get("dataTypeName", dtype)
            for value in point.get("value", []):
                if "step_count.delta" in ptype:
                    metrics["steps"] += int(value.get("intVal") or 0)
                elif "calories.expended" in ptype:
                    metrics["total_kcal"] += float(value.get("fpVal") or value.get("intVal") or 0)
    return metrics

def main():
    token = os.environ.get("GOOGLE_FIT_ACCESS_TOKEN")
    if not token:
        print("ERROR: set GOOGLE_FIT_ACCESS_TOKEN with fitness.activity.read scope", file=sys.stderr)
        return 2

    zone = ZoneInfo(TZ)
    start = date.fromisoformat(os.environ.get("GOOGLE_FIT_START", "2015-01-01"))
    today = datetime.now(zone).date()
    end = date.fromisoformat(os.environ.get("GOOGLE_FIT_END", today.isoformat()))
    records = []
    cursor = start
    chunks = 0
    while cursor <= end:
        chunk_end = min(cursor + timedelta(days=29), end)
        body = {
            "aggregateBy": [
                {"dataSourceId": "derived:com.google.step_count.delta:com.google.android.gms:estimated_steps"},
                {"dataTypeName": "com.google.calories.expended"},
            ],
            "bucketByTime": {
                "period": {"type": "day", "value": 1, "timeZoneId": TZ}
            },
            "startTimeMillis": millis(cursor, zone),
            "endTimeMillis": millis(chunk_end + timedelta(days=1), zone),
        }
        fit = request_json(FIT_URL, body, {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json;encoding=utf-8",
        })
        chunks += 1
        for b in fit.get("bucket", []):
            start_ms = int(b["startTimeMillis"])
            end_ms = int(b["endTimeMillis"])
            local = datetime.fromtimestamp(start_ms / 1000, zone).date().isoformat()
            metrics = extract_metrics(b)
            records.append({
                "type": "daily_aggregate",
                "subject": SUBJECT,
                "source": SOURCE,
                "local_date": local,
                "period_start": datetime.fromtimestamp(start_ms / 1000, ZoneInfo("UTC")).isoformat().replace("+00:00", "Z"),
                "period_end": datetime.fromtimestamp(end_ms / 1000, ZoneInfo("UTC")).isoformat().replace("+00:00", "Z"),
                "steps": metrics["steps"],
                "total_kcal": metrics["total_kcal"],
                # Match existing Health Connect aggregate key shape so cloud totals replace zero local rows idempotently.
                "record_key": f"daily_aggregate:{SUBJECT}:{local}",
            })
        cursor = chunk_end + timedelta(days=1)

    payload = {"subject": SUBJECT, "timezone": TZ, "records": records}
    result = request_json(INGEST_URL, payload, {
        "Authorization": f"Bearer {HEALTH_TOKEN}",
        "Content-Type": "application/json",
    })
    nonzero = sum(1 for r in records if r["steps"] > 0)
    print(json.dumps({
        "status": "ok",
        "fit_chunks": chunks,
        "records_posted": len(records),
        "nonzero_step_days": nonzero,
        "step_sum": sum(r["steps"] for r in records),
        "total_kcal_sum": round(sum(r.get("total_kcal", 0) for r in records), 2),
        "ingest": result,
    }, indent=2))
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
