#!/usr/bin/env python3
import json, os, sys, urllib.request, urllib.error
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='chicho'; TZ='America/Argentina/Buenos_Aires'; SOURCE='google_fit_rest'
AGG=[{'dataSourceId':'derived:com.google.step_count.delta:com.google.android.gms:estimated_steps'},{'dataTypeName':'com.google.calories.expended'},{'dataTypeName':'com.google.distance.delta'},{'dataTypeName':'com.google.heart_rate.bpm'},{'dataTypeName':'com.google.weight'},{'dataTypeName':'com.google.body.fat.percentage'},{'dataTypeName':'com.google.height'},{'dataTypeName':'com.google.hydration'},{'dataTypeName':'com.google.nutrition'}]
def millis(d,z): return int(datetime.combine(d,time.min,tzinfo=z).timestamp()*1000)
def iso(ms,z=ZoneInfo('UTC')): return datetime.fromtimestamp(int(ms)/1000,z).isoformat().replace('+00:00','Z')
def req(url,body=None,headers=None):
 data=json.dumps(body).encode() if body is not None else None
 r=urllib.request.Request(url,data=data,headers=headers or {},method='POST' if body is not None else 'GET')
 try:
  with urllib.request.urlopen(r,timeout=90) as resp: return json.loads(resp.read().decode())
 except urllib.error.HTTPError as e: raise RuntimeError(f'HTTP {e.code}: {e.read().decode()}')
def num(v): return float(v.get('fpVal') if v.get('fpVal') is not None else (v.get('intVal') or 0))
def local(ms,z): return datetime.fromtimestamp(int(ms)/1000,z).date().isoformat()
def main():
 token=os.environ.get('GOOGLE_FIT_ACCESS_TOKEN')
 if not token: print('missing token',file=sys.stderr); return 2
 z=ZoneInfo(TZ); start=date.fromisoformat(os.environ.get('GOOGLE_FIT_START','2015-01-01')); end=date.fromisoformat(os.environ.get('GOOGLE_FIT_END',datetime.now(z).date().isoformat()))
 days={}; chunks=0
 cur=start
 while cur<=end:
  ce=min(cur+timedelta(days=29),end)
  body={'aggregateBy':AGG,'bucketByTime':{'period':{'type':'day','value':1,'timeZoneId':TZ}},'startTimeMillis':millis(cur,z),'endTimeMillis':millis(ce+timedelta(days=1),z)}
  fit=req(FIT_URL,body,{'Authorization':f'Bearer {token}','Content-Type':'application/json;encoding=utf-8'}); chunks+=1
  for b in fit.get('bucket',[]):
   d=local(b['startTimeMillis'],z); rec=days.setdefault(d,{'type':'daily_aggregate','subject':SUBJECT,'source':SOURCE,'local_date':d,'period_start':iso(b['startTimeMillis']),'period_end':iso(b['endTimeMillis']),'record_key':f'daily_aggregate:{SUBJECT}:{d}','steps':0,'total_kcal':0.0})
   for ds in b.get('dataset',[]):
    for p in ds.get('point',[]):
     dtype=p.get('dataTypeName',''); vals=p.get('value',[])
     if not vals: continue
     if dtype=='com.google.step_count.delta': rec['steps'] += sum(int(v.get('intVal') or 0) for v in vals)
     elif dtype=='com.google.calories.expended': rec['total_kcal'] += sum(num(v) for v in vals)
     elif dtype=='com.google.distance.delta': rec['distance_m']=rec.get('distance_m',0.0)+sum(num(v) for v in vals)
     elif 'heart_rate' in dtype:
      nums=[num(v) for v in vals if v.get('fpVal') is not None or v.get('intVal') is not None]
      if nums: rec.setdefault('_hr_values',[]).extend(nums)
     elif 'weight' in dtype and vals: rec['weight_kg']=num(vals[0])
     elif 'body.fat' in dtype and vals: rec['body_fat_percentage']=num(vals[0])
     elif 'height' in dtype and vals: rec['height_m']=num(vals[0])
     elif 'hydration' in dtype: rec['hydration_ml']=rec.get('hydration_ml',0.0)+sum(num(v)*1000 for v in vals)
     elif 'nutrition' in dtype:
      for v in vals:
       for item in v.get('mapVal') or []:
        key=item.get('key'); val=num(item.get('value',{}))
        if key=='calories': rec['nutrition_kcal']=rec.get('nutrition_kcal',0)+val
        elif key=='protein': rec['protein_g']=rec.get('protein_g',0)+val
        elif key=='total_carbs': rec['carbs_g']=rec.get('carbs_g',0)+val
        elif key=='total_fat': rec['fat_g']=rec.get('fat_g',0)+val
  cur=ce+timedelta(days=1)
 records=[]; hrs=[]
 for d,rec in sorted(days.items()):
  hr=rec.pop('_hr_values',[]); records.append(rec)
  if hr: hrs.append({'type':'heart_rate','subject':SUBJECT,'source':SOURCE,'local_date':d,'avg_bpm':sum(hr)/len(hr),'record_key':f'heart_rate:{SUBJECT}:{d}'})
 ingest=req(INGEST_URL,{'subject':SUBJECT,'timezone':TZ,'records':records+hrs},{'Authorization':f'Bearer {HEALTH_TOKEN}','Content-Type':'application/json'})
 def cnt(f): return sum(1 for r in records if (r.get(f) or 0)>0)
 print(json.dumps({'status':'ok','chunks':chunks,'daily_records':len(records),'hr_records':len(hrs),'days_with_steps':cnt('steps'),'days_with_distance':cnt('distance_m'),'days_with_weight':cnt('weight_kg'),'days_with_nutrition':cnt('nutrition_kcal'),'days_with_hydration':cnt('hydration_ml'),'step_sum':sum(r.get('steps',0) for r in records),'distance_m_sum':round(sum(r.get('distance_m',0) for r in records),2),'ingest':ingest},indent=2))
 return 0
if __name__=='__main__': raise SystemExit(main())
