import json, sys, datetime, time
from pathlib import Path
from urllib.parse import urlencode
import urllib.request, urllib.error, re, html as htmllib

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

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

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

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

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=15) 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']
    expires_in = resp.get('expires_in', 3600)
    new['expiry'] = datetime.datetime.now(datetime.timezone.utc).timestamp() + expires_in
    new['expiry_date'] = int(time.time() * 1000) + expires_in * 1000
    new['token_type'] = 'Bearer'
    if 'scope' in resp:
        new['scopes'] = resp['scope'].split(' ')
        new['scope'] = resp['scope']
    return new

# === REFRESH + VERIFY (embedded per 2026-08-08 lesson) ===
tok = load()
access = tok.get('access_token') or tok.get('token')

# Pre-check: token clearly expired
exp = tok.get('expiry_date', 0)
now_ms = int(time.time() * 1000)
stale_by_ms = now_ms - exp if exp else 0
print(f'PRECHECK expiry_date_ms={exp} now_ms={now_ms} stale_by_s={stale_by_ms//1000}', file=sys.stderr)

# Try the API first regardless (canonical policy); 401 will trigger refresh
GMAIL_LIST = 'https://gmail.googleapis.com/gmail/v1/users/me/messages?q=is:unread%20in:inbox&maxResults=20'
CAL_URL = ('https://www.googleapis.com/calendar/v3/calendars/primary/events'
           f'?timeMin={datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")}'
           f'&timeMax={(datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=7)).strftime("%Y-%m-%dT%H:%M:%SZ")}'
           '&singleEvents=true&orderBy=startTime&maxResults=30')

status_g, data_g = api(GMAIL_LIST, access)
if status_g == 401:
    print('GMAIL 401 — refreshing', file=sys.stderr)
    new = refresh(tok)
    if not new:
        print('REFRESH_FAILED', file=sys.stderr)
        sys.exit(2)
    save(new)
    # VERIFY write
    saved = load()
    if saved.get('access_token') != new['access_token']:
        print('WRITE_FAILED access_token mismatch', file=sys.stderr)
        sys.exit(3)
    exp_after = saved.get('expiry_date', 0)
    if exp_after <= now_ms:
        print(f'WRITE_FAILED expiry_date still stale: {exp_after}', file=sys.stderr)
        sys.exit(4)
    print(f'REFRESH_OK verified, expires_in ~{(exp_after-now_ms)//1000}s', file=sys.stderr)
    tok = new
    access = tok['access_token']
    status_g, data_g = api(GMAIL_LIST, access)

status_c, data_c = api(CAL_URL, access)
if status_c == 401:
    # Try a trivial probe to disambiguate
    status_p, data_p = api('https://www.googleapis.com/calendar/v3/calendars/primary', access)
    if status_p == 401:
        print('CALENDAR 401 — degraded', file=sys.stderr)
        data_c = None
    else:
        data_c = data_p  # events list specifically failed, but calendar works

if status_g != 200:
    print(f'GMAIL_FAIL status={status_g}', file=sys.stderr)
    sys.exit(5)

# === PROCESS GMAIL ===
ids = [m['id'] for m in data_g.get('messages', [])][:15]
print(f'GMAIL_OK unread_count={len(ids)}', file=sys.stderr)

results = []
for mid in ids:
    url = f'https://gmail.googleapis.com/gmail/v1/users/me/messages/{mid}?format=full'
    s, d = api(url, access)
    if s != 200:
        continue
    payload = d.get('payload', {})
    headers = {h['name']: h['value'] for h in payload.get('headers', [])}
    snippet = d.get('snippet', '')
    int_date = int(d.get('internalDate', 0))
    results.append({
        'id': mid,
        'from': headers.get('From', ''),
        'subject': headers.get('Subject', ''),
        'date_hdr': headers.get('Date', ''),
        'internalDate': int_date,
        'snippet': snippet,
        'labels': d.get('labelIds', []),
        'payload': payload,
    })

# Filter noise: CATEGORY_PROMOTIONS, CATEGORY_SOCIAL, CATEGORY_UPDATES
NOISE_LABELS = {'CATEGORY_PROMOTIONS', 'CATEGORY_SOCIAL', 'CATEGORY_UPDATES'}
NOISE_SENDERS = ('noreply@', 'no-reply@', 'notifications@', 'mail-noreply@')
NOISE_SUBJECTS = ('2-step verification', '2SV', 'security alert', 'verify your', 'Wispr Flow', 'Airtable', 'Stripe')

def is_noise(r):
    if any(lab in r['labels'] for lab in NOISE_LABELS):
        return True
    f = r['from'].lower()
    if any(n in f for n in NOISE_SENDERS):
        return True
    sub = r['subject'].lower()
    if any(n.lower() in sub for n in NOISE_SUBJECTS):
        return True
    if r['subject'].startswith('Recall:'):
        return True
    return False

# Sort by internalDate desc, drop noise first then promote real
results.sort(key=lambda r: r['internalDate'], reverse=True)
real = [r for r in results if not is_noise(r)]
noisy = [r for r in results if is_noise(r)]

# Top 5 from real; fall back if < 2
top = real[:5]
if len(top) < 2 and noisy:
    top = (real + noisy)[:5]

def decode_body(payload):
    body = payload.get('body', {})
    if body.get('data'):
        raw = body['data']
        padded = raw + '=' * (-len(raw) % 4)
        try:
            return base64.urlsafe_b64decode(padded).decode('utf-8', 'replace')
        except Exception:
            return ''
    for p in payload.get('parts', []):
        if p.get('mimeType') == 'text/plain':
            raw = p.get('body', {}).get('data', '')
            if raw:
                padded = raw + '=' * (-len(raw) % 4)
                try:
                    return base64.urlsafe_b64decode(padded).decode('utf-8', 'replace')
                except Exception:
                    return ''
    for p in payload.get('parts', []):
        t = decode_body(p)
        if t:
            return t
    return ''

def decode_body_html(payload):
    if payload.get('mimeType') == 'text/html' and payload.get('body', {}).get('data'):
        raw = payload['body']['data']
        padded = raw + '=' * (-len(raw) % 4)
        try:
            return base64.urlsafe_b64decode(padded).decode('utf-8', 'replace')
        except Exception:
            return ''
    for p in payload.get('parts', []):
        t = decode_body_html(p)
        if t:
            return t
    return ''

import base64

def html_to_text(h):
    t = re.sub(r'<style[^>]*>.*?</style>', '', h, flags=re.S | re.I)
    t = re.sub(r'<[^>]+>', ' ', t)
    t = htmllib.unescape(t)
    t = re.sub(r'\s+', ' ', t)
    return t.strip()

def sydney_time(ms):
    dt = datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc).astimezone(TZ_SYDNEY)
    return dt.strftime('%a %H:%M')

def short_sender(f):
    # "Name <addr>" -> "Name"; fallback to addr
    m = re.match(r'^"?([^"<]+?)"?\s*<', f)
    if m:
        return m.group(1).strip()
    return f.split('@')[0] if '@' in f else f

# Build top 5 lines, optionally body-extracting tender-forward candidates
TENDER_HINTS = ('TENDER', 'RFT', 'RFQ', 'RFI', 'Concrete FRP', 'FRP Works', 'Tender Request', 'ProcurePro', 'EstimateOne')

top_lines = []
for r in top:
    sender = short_sender(r['from'])
    subj = r['subject']
    snippet = r['snippet'][:160].replace('\n', ' ')

    # If Liam fwd with tender hint OR snippet is signature-only, try body
    extra = ''
    f_lower = r['from'].lower()
    if ('liam' in f_lower or 'fitzgerald' in f_lower or 'info@lfcs' in f_lower) and (
        any(h.lower() in subj.lower() for h in TENDER_HINTS) or len(snippet) < 80
    ):
        plain = decode_body(r['payload'])
        if not plain or len(plain.strip()) < 20:
            html_b = decode_body_html(r['payload'])
            if html_b:
                plain = html_to_text(html_b)
        if plain:
            # Look for sender inside forwarded delimiter
            m = re.search(r'From:\s*([^\n<]+<[^>]+>)', plain)
            if m:
                inner = short_sender(m.group(1))
                extra_sender = f' (fwd {inner})'
            else:
                extra_sender = ''
            # Look for deadline
            deadline_match = re.search(
                r'(Tender due date|Due|Closing|Submission deadline|Closes|Submit by)[:\s]+([^\n]+)',
                plain, re.I
            )
            if deadline_match:
                extra = f' — {deadline_match.group(2).strip()[:60]}'
            elif extra_sender:
                extra = f'{extra_sender} — '
            else:
                # body snippet — first non-signature line
                body_lines = [l.strip() for l in plain.split('\n') if l.strip()]
                # skip signature lines
                content_lines = [l for l in body_lines if not l.startswith('--') and 'Kind regards' not in l and 'Fitzgerald' not in l]
                if content_lines:
                    extra = f' — {content_lines[0][:80]}'

    top_lines.append({
        'time': sydney_time(r['internalDate']),
        'sender': sender,
        'subject': subj,
        'snippet': snippet,
        'extra': extra,
    })

# === PROCESS CALENDAR ===
events = []
if data_c and 'items' in data_c:
    for ev in data_c['items']:
        start = ev.get('start', {})
        when = start.get('dateTime') or start.get('date', '')
        title = ev.get('summary', '(no title)')
        loc = ev.get('location', '')
        events.append({'when': when, 'title': title, 'loc': loc})

# === OUTPUT JSON for assembly ===
out = {
    'top': top_lines,
    'noisy_count': len(noisy),
    'events': events,
    'unread_total': len(ids),
    'real_count': len(real),
}
print(json.dumps(out))
