#!/usr/bin/env python3
"""Harvey's Calendar Tool - Uses Google Service Account with domain-wide delegation.

Usage:
  calendar.py agenda [--days N] [--cal CALENDAR]
  calendar.py today
  calendar.py tomorrow
  calendar.py week
  calendar.py add --cal CALENDAR --title TITLE --start "YYYY-MM-DD HH:MM" [--end "YYYY-MM-DD HH:MM"] [--duration MINUTES] [--desc DESCRIPTION] [--location LOCATION]
  calendar.py delete --cal CALENDAR --event-id EVENT_ID
  calendar.py search --query QUERY [--days N]
  calendar.py list-calendars
  calendar.py free --date YYYY-MM-DD

Calendars (shortcuts):
  work     = mike@nomorechores.com
  personal = mikeziarko@gmail.com
  schedule = Personal Schedule
  launch27 = Launch27
  all      = all calendars (default for agenda)
"""

import argparse
import json
import sys
from datetime import datetime, timedelta, timezone, tzinfo
from zoneinfo import ZoneInfo
from google.oauth2 import service_account
from googleapiclient.discovery import build

SA_FILE = '/Users/harvey/.openclaw/secrets/google-calendar-sa.json'
SCOPES = ['https://www.googleapis.com/auth/calendar']
SUBJECT = 'mike@nomorechores.com'
TZ = ZoneInfo('America/Toronto')

CALENDAR_MAP = {
    'work': 'mike@nomorechores.com',
    'business': 'mike@nomorechores.com',
    'nmc': 'mike@nomorechores.com',
    'personal': 'mikeziarko@gmail.com',
    'schedule': 'c_7b722nur02urvj6qi9sud2rgqc@group.calendar.google.com',
    'launch27': 'c_e565aef4a3632aad141aa02956b4c5031a9d87257b10b54ef69356cf2eab4a3f@group.calendar.google.com',
}

def get_service():
    creds = service_account.Credentials.from_service_account_file(SA_FILE, scopes=SCOPES)
    delegated = creds.with_subject(SUBJECT)
    return build('calendar', 'v3', credentials=delegated, cache_discovery=False)

def resolve_calendar(name):
    if not name or name == 'all':
        return None
    return CALENDAR_MAP.get(name.lower(), name)

def get_all_calendar_ids(service):
    result = service.calendarList().list().execute()
    return [c['id'] for c in result.get('items', [])]

def format_event(event):
    start = event['start'].get('dateTime', event['start'].get('date', ''))
    end = event['end'].get('dateTime', event['end'].get('date', ''))
    summary = event.get('summary', '(no title)')
    location = event.get('location', '')
    event_id = event.get('id', '')
    
    # Parse and format time
    try:
        if 'T' in start:
            dt_start = datetime.fromisoformat(start).astimezone(TZ)
            dt_end = datetime.fromisoformat(end).astimezone(TZ)
            time_str = f"{dt_start.strftime('%a %b %d %I:%M %p')} - {dt_end.strftime('%I:%M %p')}"
        else:
            dt_start = datetime.strptime(start, '%Y-%m-%d')
            time_str = f"{dt_start.strftime('%a %b %d')} (all day)"
    except Exception:
        time_str = start

    line = f"  {time_str} | {summary}"
    if location:
        line += f" @ {location}"
    line += f"  [id:{event_id}]"
    return line

def cmd_agenda(args):
    service = get_service()
    now = datetime.now(TZ)
    end = now + timedelta(days=args.days)
    
    cal_id = resolve_calendar(args.cal)
    cal_ids = [cal_id] if cal_id else get_all_calendar_ids(service)
    
    all_events = []
    for cid in cal_ids:
        try:
            events = service.events().list(
                calendarId=cid,
                timeMin=now.isoformat(),
                timeMax=end.isoformat(),
                singleEvents=True,
                orderBy='startTime',
                maxResults=50
            ).execute()
            for e in events.get('items', []):
                e['_calendar'] = cid
                all_events.append(e)
        except Exception as ex:
            print(f"  (error reading {cid}: {ex})", file=sys.stderr)
    
    # Sort by start time
    def sort_key(e):
        s = e['start'].get('dateTime', e['start'].get('date', ''))
        return s
    all_events.sort(key=sort_key)
    
    if not all_events:
        print(f"No events in the next {args.days} day(s).")
        return
    
    print(f"📅 Next {args.days} day(s) — {len(all_events)} events:\n")
    current_date = None
    for e in all_events:
        s = e['start'].get('dateTime', e['start'].get('date', ''))
        try:
            if 'T' in s:
                d = datetime.fromisoformat(s).astimezone(TZ).date()
            else:
                d = datetime.strptime(s, '%Y-%m-%d').date()
            if d != current_date:
                current_date = d
                print(f"\n{'─' * 40}")
                print(f"  {d.strftime('%A, %B %d')}")
                print(f"{'─' * 40}")
        except Exception:
            pass
        print(format_event(e))

def cmd_add(args):
    service = get_service()
    cal_id = resolve_calendar(args.cal)
    if not cal_id:
        cal_id = CALENDAR_MAP['personal']  # default to personal
    
    # Parse start time
    start_dt = datetime.strptime(args.start, '%Y-%m-%d %H:%M').replace(tzinfo=TZ)
    
    if args.end:
        end_dt = datetime.strptime(args.end, '%Y-%m-%d %H:%M').replace(tzinfo=TZ)
    else:
        duration = args.duration or 60
        end_dt = start_dt + timedelta(minutes=duration)
    
    event_body = {
        'summary': args.title,
        'start': {'dateTime': start_dt.isoformat(), 'timeZone': 'America/Toronto'},
        'end': {'dateTime': end_dt.isoformat(), 'timeZone': 'America/Toronto'},
    }
    if args.desc:
        event_body['description'] = args.desc
    if args.location:
        event_body['location'] = args.location
    
    event = service.events().insert(calendarId=cal_id, body=event_body).execute()
    print(f"✅ Created: {args.title}")
    print(f"   {start_dt.strftime('%a %b %d %I:%M %p')} - {end_dt.strftime('%I:%M %p')}")
    print(f"   Calendar: {cal_id}")
    print(f"   Event ID: {event['id']}")

def cmd_delete(args):
    service = get_service()
    cal_id = resolve_calendar(args.cal)
    if not cal_id:
        print("Error: --cal required for delete", file=sys.stderr)
        sys.exit(1)
    
    service.events().delete(calendarId=cal_id, eventId=args.event_id).execute()
    print(f"✅ Deleted event {args.event_id} from {cal_id}")

def cmd_search(args):
    service = get_service()
    now = datetime.now(TZ)
    end = now + timedelta(days=args.days)
    cal_ids = get_all_calendar_ids(service)
    
    all_events = []
    for cid in cal_ids:
        try:
            events = service.events().list(
                calendarId=cid,
                timeMin=now.isoformat(),
                timeMax=end.isoformat(),
                q=args.query,
                singleEvents=True,
                orderBy='startTime'
            ).execute()
            all_events.extend(events.get('items', []))
        except Exception:
            pass
    
    if not all_events:
        print(f"No events matching '{args.query}' in the next {args.days} days.")
        return
    
    print(f"🔍 Found {len(all_events)} event(s) matching '{args.query}':\n")
    for e in all_events:
        print(format_event(e))

def cmd_list_calendars(args):
    service = get_service()
    result = service.calendarList().list().execute()
    print("📋 Available calendars:\n")
    for cal in result.get('items', []):
        shortcut = next((k for k, v in CALENDAR_MAP.items() if v == cal['id']), None)
        label = f" (shortcut: {shortcut})" if shortcut else ""
        print(f"  {cal.get('summary', '(unnamed)')}{label}")
        print(f"    ID: {cal['id']}")

def cmd_free(args):
    """Show free/busy blocks for a given date."""
    service = get_service()
    date = datetime.strptime(args.date, '%Y-%m-%d').replace(tzinfo=TZ)
    day_start = date.replace(hour=7, minute=0)
    day_end = date.replace(hour=22, minute=0)
    
    cal_ids = get_all_calendar_ids(service)
    body = {
        'timeMin': day_start.isoformat(),
        'timeMax': day_end.isoformat(),
        'timeZone': 'America/Toronto',
        'items': [{'id': cid} for cid in cal_ids]
    }
    
    result = service.freebusy().query(body=body).execute()
    
    # Collect all busy periods
    busy = []
    for cal_id, data in result.get('calendars', {}).items():
        for period in data.get('busy', []):
            s = datetime.fromisoformat(period['start']).astimezone(TZ)
            e = datetime.fromisoformat(period['end']).astimezone(TZ)
            busy.append((s, e))
    
    busy.sort()
    
    print(f"📅 Free/Busy for {date.strftime('%A, %B %d')} (7am-10pm):\n")
    
    if not busy:
        print("  Completely free!")
        return
    
    # Merge overlapping
    merged = [busy[0]]
    for s, e in busy[1:]:
        if s <= merged[-1][1]:
            merged[-1] = (merged[-1][0], max(merged[-1][1], e))
        else:
            merged.append((s, e))
    
    # Show busy blocks
    print("  BUSY:")
    for s, e in merged:
        print(f"    {s.strftime('%I:%M %p')} - {e.strftime('%I:%M %p')}")
    
    # Show free blocks
    print("\n  FREE:")
    cursor = day_start
    for s, e in merged:
        if cursor < s:
            print(f"    {cursor.strftime('%I:%M %p')} - {s.strftime('%I:%M %p')}")
        cursor = max(cursor, e)
    if cursor < day_end:
        print(f"    {cursor.strftime('%I:%M %p')} - {day_end.strftime('%I:%M %p')}")

def main():
    parser = argparse.ArgumentParser(description='Harvey Calendar Tool')
    sub = parser.add_subparsers(dest='command')
    
    # agenda
    p_agenda = sub.add_parser('agenda')
    p_agenda.add_argument('--days', type=int, default=3)
    p_agenda.add_argument('--cal', default='all')
    
    # today/tomorrow/week shortcuts
    sub.add_parser('today')
    sub.add_parser('tomorrow')
    sub.add_parser('week')
    
    # add
    p_add = sub.add_parser('add')
    p_add.add_argument('--cal', default='personal')
    p_add.add_argument('--title', required=True)
    p_add.add_argument('--start', required=True, help='YYYY-MM-DD HH:MM')
    p_add.add_argument('--end', help='YYYY-MM-DD HH:MM')
    p_add.add_argument('--duration', type=int, help='Duration in minutes (default 60)')
    p_add.add_argument('--desc', help='Description')
    p_add.add_argument('--location', help='Location')
    
    # delete
    p_del = sub.add_parser('delete')
    p_del.add_argument('--cal', required=True)
    p_del.add_argument('--event-id', required=True)
    
    # search
    p_search = sub.add_parser('search')
    p_search.add_argument('--query', required=True)
    p_search.add_argument('--days', type=int, default=30)
    
    # list-calendars
    sub.add_parser('list-calendars')
    
    # free
    p_free = sub.add_parser('free')
    p_free.add_argument('--date', required=True, help='YYYY-MM-DD')
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        sys.exit(1)
    
    # Shortcuts
    if args.command == 'today':
        args.days = 1
        args.cal = 'all'
        cmd_agenda(args)
    elif args.command == 'tomorrow':
        args.days = 2
        args.cal = 'all'
        cmd_agenda(args)
    elif args.command == 'week':
        args.days = 7
        args.cal = 'all'
        cmd_agenda(args)
    elif args.command == 'agenda':
        cmd_agenda(args)
    elif args.command == 'add':
        cmd_add(args)
    elif args.command == 'delete':
        cmd_delete(args)
    elif args.command == 'search':
        cmd_search(args)
    elif args.command == 'list-calendars':
        cmd_list_calendars(args)
    elif args.command == 'free':
        cmd_free(args)

if __name__ == '__main__':
    main()
