import base64, datetime as dt, html, json, re, time
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError

TOKEN=Path('/root/.hermes/google_token.json')
REAUTH='WARNING: Morning digest unavailable — Google OAuth token needs re-auth. Run: python3 /usr/local/lib/hermes-agent/setup.py --auth-url'

def load(): return json.loads(TOKEN.read_text())
def api(url, tok):
    req=Request(url, headers={'Authorization':'Bearer '+tok})
    try:
        with urlopen(req, timeout=25) as r: return r.status, json.loads(r.read())
    except HTTPError as e: return e.code, e.read().decode(errors='replace')
def refresh(t):
    if not t.get('refresh_token'): return False
    data=urlencode({'client_id':t.get('client_id',''),'client_secret':t.get('client_secret',''),'refresh_token':t['refresh_token'],'grant_type':'refresh_token'}).encode()
    try:
        req=Request('https://oauth2.googleapis.com/token',data=data,headers={'Content-Type':'application/x-www-form-urlencoded'})
        with urlopen(req,timeout=25) as r: n=json.loads(r.read())
        if 'access_token' not in n: return False
        t.update(n); t['token']=n['access_token']; t['expiry']=time.time()+n.get('expires_in',3600); TOKEN.write_text(json.dumps(t,indent=2));
        saved=load()
        return saved.get('access_token')==n['access_token']
    except Exception: return False

def get(url,t):
    tok=t.get('access_token') or t.get('token'); s,d=api(url,tok)
    if s==401 and refresh(t): s,d=api(url,load().get('access_token'))
    return s,d

def hdr(m,n):
    return next((x['value'] for x in m.get('payload',{}).get('headers',[]) if x['name'].lower()==n.lower()),'')
def body(p, want_html=False):
    if p.get('body',{}).get('data'):
        try:return base64.urlsafe_b64decode(p['body']['data']+'='*((-len(p['body']['data']))%4)).decode(errors='replace')
        except:pass
    for x in p.get('parts',[]):
        if x.get('mimeType')==('text/html' if want_html else 'text/plain'):
            z=body(x,want_html)
            if z:return z
    for x in p.get('parts',[]):
        z=body(x,want_html)
        if z:return z
    return ''
def clean(s): return re.sub(r'\\s+',' ',re.sub(r'<[^>]+>',' ',html.unescape(s))).strip()
def syd(ms):
    from zoneinfo import ZoneInfo
    return dt.datetime.fromtimestamp(int(ms)/1000,dt.timezone.utc).astimezone(ZoneInfo('Australia/Sydney'))
def main():
    t=load(); tok=t.get('access_token') or t.get('token')
    now=dt.datetime.now(dt.timezone.utc); end=now+dt.timedelta(days=7)
    s,g=get('https://gmail.googleapis.com/gmail/v1/users/me/messages?'+urlencode({'q':'is:unread in:inbox','maxResults':50}),t)
    if s==401: print(REAUTH); return
    if s!=200: print(REAUTH); return
    msgs=[]
    for x in g.get('messages',[]):
        ss,m=get('https://gmail.googleapis.com/gmail/v1/users/me/messages/'+x['id']+'?format=full',t)
        if ss==200: msgs.append(m)
    cs,cal=get('https://www.googleapis.com/calendar/v3/calendars/primary/events?'+urlencode({'timeMin':now.strftime('%Y-%m-%dT%H:%M:%SZ'),'timeMax':end.strftime('%Y-%m-%dT%H:%M:%SZ'),'singleEvents':'true','orderBy':'startTime','maxResults':30}),t)
    if cs==401: print(REAUTH); return
    relevant=[]
    for m in sorted(msgs,key=lambda z:int(z.get('internalDate','0')),reverse=True):
        subj=hdr(m,'Subject'); frm=hdr(m,'From'); sn=clean(m.get('snippet',''))
        if subj.lower().startswith('recall:'): continue
        if any(x in (frm+' '+subj).lower() for x in ['wispr','stripe','unsubscribe','privacy update']): continue
        relevant.append((frm,subj,sn,m))
    lines=['Morning Digest — '+syd(time.time()*1000).strftime('%a %d %b'),'','## Deadlines today','- (none found in Gmail or Calendar)','', '## Action items']
    lines.append('- Nothing urgent identified from the fetched unread Gmail.')
    lines += ['',f'## Gmail — top 5 unread ({len(msgs)} total)']
    for frm,subj,sn,m in relevant[:5]: lines.append(f'- {frm.split("<")[0].strip()} — {subj}: {sn[:140]} — {syd(m["internalDate"]).strftime("%d %b %H:%M")}')
    if not relevant: lines.append('- No unread Gmail requiring action.')
    lines += ['', '## Calendar — next 7 days']
    evs=cal.get('items',[]) if isinstance(cal,dict) else []
    if not evs: lines.append('- Nothing scheduled')
    for e in evs:
        st=e.get('start',{}).get('dateTime') or e.get('start',{}).get('date','')
        lines.append(f'- {st} — {e.get("summary","(untitled)")}'+(f' — {e.get("location")}' if e.get('location') else ''))
    lines += ['', '## Waiting on (3+ days, open follow-ups)', '- Nothing stale (MEMORY.md contains no open follow-ups)']
    print('\n'.join(lines)[:1700])
main()
