#!/usr/bin/env python3
"""Morning digest fetch — 2026-07-22 canonical recipe.
API-first, refresh-on-401, no bash loops, no *** markers."""
import json, datetime, sys, os, subprocess
from pathlib import Path
from urllib.parse import urlencode
import urllib.request, urllib.error

TOKEN_PATH = Path.home() / '.hermes' / 'google_token.json'
TZ = datetime.timezone(datetime.timedelta(hours=10))  # AEST


def load():
    return json.loads(TOKEN_PATH.read_text())


def save(t):
    TOKEN_PATH.write_text(json.dumps(t, indent=2))
    os.chmod(TOKEN_PATH, 0o600)


def api(url, tok):
    req = urllib.request.Request(url, headers={'Authorization': f'Bearer {tok}'})
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return r.status, json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        return e.code, e.read().decode()[:400]


def refresh(tok):
    form = urlencode({
        'client_id': tok['client_id'],
        'client_secret': tok['client_secret'],
        'refresh_token': tok['refresh_token'],
        'grant_type': 'refresh_token',
    }).encode()
    req = urllib.request.Request(
        'https://oauth2.googleapis.com/token',
        data=form,
        headers={'Content-Type': 'application/x-www-form-urlencoded'},
    )
    with urllib.request.urlopen(req, timeout=20) as r:
        resp = json.loads(r.read().decode())
    if 'access_token' not in resp:
        return None
    new = dict(tok)
    new['access_token'] = resp['access_token']
    new['token'] = resp['access_token']
    new['expiry'] = datetime.datetime.now(datetime.timezone.utc).timestamp() + resp.get('expires_in', 3600)
    new['expiry_date'] = int(datetime.datetime.now(datetime.timezone.utc).timestamp() * 1000) + resp.get('expires_in', 3600) * 1000
    if 'scope' in resp:
        new['scopes'] = resp['scope'].split(' ')
    return new


def fetch_unread(tok, max_results=20):
    url = (
        'https://gmail.googleapis.com/gmail/v1/users/me/messages'
        '?q=is:unread%20in:inbox%20-category:promotions%20-category:social'
        '&maxResults=' + str(max_results)
    )
    status, data = api(url, tok)
    return status, data


def fetch_meta(mid, tok):
    url = (
        f'https://gmail.googleapis.com/gmail/v1/users/me/messages/{mid}'
        '?format=full'
    )
    status, data = api(url, tok)
    return status, data


def fetch_calendar(tok):
    now = datetime.datetime.now(datetime.timezone.utc)
    end = now + datetime.timedelta(days=7)
    url = (
        'https://www.googleapis.com/calendar/v3/calendars/primary/events'
        '?timeMin=' + now.strftime('%Y-%m-%dT%H:%M:%SZ')
        + '&timeMax=' + end.strftime('%Y-%m-%dT%H:%M:%SZ')
        + '&singleEvents=true&orderBy=startTime&maxResults=30'
    )
    return api(url, tok)


def main():
    tok = load()
    access = tok.get('access_token') or tok.get('token')

    # Gmail list
    status, data = fetch_unread(access)
    if status == 401:
        new = refresh(tok)
        if not new:
            print('REAUTH_REQUIRED')
            sys.exit(2)
        tok = new
        save(tok)
        access = tok['access_token']
        status, data = fetch_unread(access)
        if status == 401:
            print('REAUTH_REQUIRED')
            sys.exit(2)

    if status != 200:
        print(json.dumps({'status': status, 'body': data}))
        sys.exit(3)

    msg_list = data.get('messages', [])

    # Fetch metadata for each (default format=full per 2026-07-18 fix)
    enriched = []
    for m in msg_list:
        s, d = fetch_meta(m['id'], access)
        if s != 200:
            continue
        payload = d.get('payload', {})
        headers = {h['name']: h['value'] for h in payload.get('headers', [])}
        # Skip if in promotions/social (filter idempotently)
        labels = d.get('labelIds', [])
        if 'CATEGORY_PROMOTIONS' in labels or 'CATEGORY_SOCIAL' in labels:
            continue
        # Skip google 2SV/security notifications noise
        sender = headers.get('From', '')
        subject = headers.get('Subject', '')
        if 'google.com' in sender.lower() and any(k in subject.lower() for k in ['2-step', '2sv', 'verify', 'security', 'password']):
            continue
        enriched.append({
            'id': m['id'],
            'from': sender,
            'subject': subject,
            'date': headers.get('Date', ''),
            'internal_date': d.get('internalDate'),
            'snippet': d.get('snippet', ''),
            'labels': labels,
        })

    # Calendar
    cs, cd = fetch_calendar(access)
    if cs == 401:
        # try refresh once
        new = refresh(tok)
        if new:
            tok = new; save(tok); access = tok['access_token']
            cs, cd = fetch_calendar(access)
    events = cd.get('items', []) if cs == 200 else []

    out = {
        'unread_total': len(msg_list),
        'enriched': enriched,
        'calendar_status': cs,
        'events': events,
    }
    print(json.dumps(out, indent=2, default=str))


if __name__ == '__main__':
    main()
