#!/usr/bin/env python3
"""
Google Drive Reorganization Script for No More Chores
Date: 2026-03-28
"""

import json
import time
import urllib.request
import urllib.parse
import urllib.error
import base64
import hmac
import hashlib
import struct
import datetime
import os

# ── Config ────────────────────────────────────────────────────────────────────
SA_KEY_PATH = "/Users/harvey/.openclaw/secrets/google-calendar-sa.json"
IMPERSONATE = "mike@nomorechores.com"
SCOPE = "https://www.googleapis.com/auth/drive"
NMC_ROOT = "0B3Rg9CVvNeIJQVhFWEFZM1VFWWs"
FINANCES_ROOT = "0B3Rg9CVvNeIJSlJqLVNURC00Wms"
CONTRACTORS_ROOT = "1b-BAiWC5MhRZk15kdCKb6XqLAscb92qc"
KEEP_BOOKKEEPING_ID = "10nts9dTNb4Z11aem9eUmkxX0rejPz5uw"

DEST = {
    "Strategy": "1C0Mqa6YVA0xdDZo8fRnSiFPl3Pjl0Mos",
    "Budgets": "1YVr8wmwGWgPnKC8Nvs1RPqf_BD6EQnkV",
    "Bookkeeping": "10nts9dTNb4Z11aem9eUmkxX0rejPz5uw",
    "SOPs": "1aDJkGPdyJaNBYwN_XR8RBlljfau9v-C6",
    "HR": "16PC5goXad69KeSlrRGxbpU52tFSao3P4",
    "Marketing": "16rJSPO3vtxWC6rkrxPqPL642jRo87glL",
    "Operations": "1_X33NEBI9npmn_BpruIg6m4IwOeMHQd3",
    "Archive": "1C973EXxXHjY3tCIN3mnWV5cI8gUgy5vl",
}

# Files: id → (dest_folder_id, label)
FILES_TO_MOVE = [
    ("1XLhHrdNokXXnkypp-_qNheqsl0EMPlCsRObS7kGb57Q", DEST["Strategy"], "Goals → Strategy"),
    ("1qGG78hr2Qr43L-45LpzeUwb_-o0LKoGqOygxLlQGDkA", DEST["Budgets"], "WhatIf → Budgets"),
    ("1QdzINoSaxjxw9eRBTg1Y7jWHoRhdveAT", DEST["Budgets"], "MaidService calc → Budgets"),
    ("19Il7oHaaDklJP5xvMSZh_Ie14MfQEiJO", DEST["SOPs"], "NMC Checklist new → SOPs"),
    ("1TwCbEP77phNqSB8YKL4cw-jhjHH6dGnj", DEST["SOPs"], "NMC Checklist orig → SOPs"),
    ("1SelQTyfc3NySsKXbxEra89zVcrjCGbz72fNtfX9jkBE", DEST["HR"], "Pay Abigael 11-2024 → HR"),
    ("17nxJfvTDUr5zKZjXt4tKe5i4ivi-Ur4T_E2SxY0UIk4", DEST["HR"], "Pay Abigael 9-2023 → HR"),
    ("1eMKaZvr2fjabtdsn3PfY1gMxWJLG9flP69KybEbjKBU", DEST["HR"], "Pay Fez → HR"),
    ("1-cssN0ylAhGXC1qhscNSlkwaUZ3Z06UYEkk086rq6GM", DEST["HR"], "Pay Grace → HR"),
    # Helen stmt → T4As & Payments (resolved at runtime)
    ("1kBvrLvVNAVvwza27vWC1Q6gN6zGeLJfgBItp44o5R5A", None, "Helen stmt → Contractors/T4As"),
    ("18Peu0ORVR0e9FTmwSP6tAg1YTV3sofRNszsGYUwjXAA", DEST["Marketing"], "Citation cleanup → Marketing"),
]

FOLDERS_TO_MOVE = [
    ("1sLPVGgQ2whwFzP8nc1bmqFlFksD5Xag-", DEST["Marketing"], "SEO → Marketing"),
    ("1vLLlYyADtBxxd2AxzMDUdLgbCgcY9sF4", DEST["Marketing"], "Sales → Marketing"),
    ("1IDE2d1ORGeAZkDAkgEtb5jgOP-G2S0V2", DEST["Operations"], "Systems → Operations"),
    ("1LYRsJtszMPbj-mwqoR0pB15eO-9JcXsM", DEST["Operations"], "The Systems Project → Operations"),
    ("1u-3ZbSNOSuFiE8HvGTUdba-Y6kqx9-en", DEST["Operations"], "Management → Operations"),
    ("1pLRajUgsHJkZaTOFTkSlRnHeH5wUe0jk", DEST["Archive"], "Commercial → Archive"),
    ("1XqZNYd3LsbZuLAP3IKP8BzEH7H6g3gjC", DEST["Archive"], "Cleaning Boss → Archive"),
    ("1ZYoIa4btjFRihkrcF1Ye1vVxGeKrGX9r", DEST["Archive"], "Mike/Amy → Archive"),
    ("0B3Rg9CVvNeIJQ3p2RUJMbWpZZVE", DEST["Archive"], "Tim Blackburn → Archive"),
]

# ── JWT / OAuth ───────────────────────────────────────────────────────────────

def b64url(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

def make_jwt(sa: dict) -> str:
    now = int(time.time())
    header = b64url(json.dumps({"alg": "RS256", "typ": "JWT"}).encode())
    payload = b64url(json.dumps({
        "iss": sa["client_email"],
        "sub": IMPERSONATE,
        "scope": SCOPE,
        "aud": "https://oauth2.googleapis.com/token",
        "iat": now,
        "exp": now + 3600,
    }).encode())
    signing_input = f"{header}.{payload}".encode()

    # Sign with RSA-SHA256 using cryptography library
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    private_key = serialization.load_pem_private_key(
        sa["private_key"].encode(), password=None
    )
    sig = private_key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
    return f"{header}.{payload}.{b64url(sig)}"

def get_access_token(sa: dict) -> str:
    jwt = make_jwt(sa)
    data = urllib.parse.urlencode({
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": jwt,
    }).encode()
    req = urllib.request.Request(
        "https://oauth2.googleapis.com/token",
        data=data,
        method="POST",
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    with urllib.request.urlopen(req) as r:
        return json.loads(r.read())["access_token"]

# ── Drive API helpers ──────────────────────────────────────────────────────────

def drive_request(token: str, method: str, path: str, params: dict = None, body: dict = None):
    base = "https://www.googleapis.com/drive/v3"
    url = base + path
    if params:
        url += "?" + urllib.parse.urlencode(params)
    data = json.dumps(body).encode() if body else None
    headers = {"Authorization": f"Bearer {token}"}
    if data:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=data, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read()) if r.read.__self__.headers.get("Content-Length") != "0" else {}
    except urllib.error.HTTPError as e:
        body_bytes = e.read()
        raise Exception(f"HTTP {e.code} {e.reason}: {body_bytes.decode()[:300]}")

def drive_get(token, path, params=None):
    base = "https://www.googleapis.com/drive/v3"
    url = base + path
    if params:
        url += "?" + urllib.parse.urlencode(params)
    req = urllib.request.Request(url, headers={"Authorization": f"Bearer {token}"})
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        raise Exception(f"HTTP {e.code} {e.reason}: {e.read().decode()[:300]}")

def drive_patch(token, file_id, add_parent, remove_parent):
    params = {
        "addParents": add_parent,
        "removeParents": remove_parent,
        "supportsAllDrives": "true",
        "fields": "id,name,parents",
    }
    url = f"https://www.googleapis.com/drive/v3/files/{file_id}?" + urllib.parse.urlencode(params)
    req = urllib.request.Request(
        url,
        data=b"{}",
        method="PATCH",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        raise Exception(f"HTTP {e.code} {e.reason}: {e.read().decode()[:300]}")

def list_files(token, query, fields="files(id,name,parents,mimeType)"):
    params = {
        "q": query,
        "fields": fields,
        "supportsAllDrives": "true",
        "includeItemsFromAllDrives": "true",
        "pageSize": "100",
    }
    result = drive_get(token, "/files", params)
    return result.get("files", [])

def get_file_parents(token, file_id):
    result = drive_get(token, f"/files/{file_id}", {
        "fields": "id,name,parents,mimeType",
        "supportsAllDrives": "true",
    })
    return result

def trash_file(token, file_id):
    url = f"https://www.googleapis.com/drive/v3/files/{file_id}/trash"
    req = urllib.request.Request(
        url,
        data=b"",
        method="POST",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
    )
    params = "?supportsAllDrives=true"
    req = urllib.request.Request(
        url + params,
        data=b"",
        method="POST",
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        raise Exception(f"HTTP {e.code} {e.reason}: {e.read().decode()[:300]}")

# ── Main ──────────────────────────────────────────────────────────────────────

results = []

def log(ok: bool, label: str, detail: str = ""):
    mark = "✅" if ok else "❌"
    msg = f"{mark} {label}"
    if detail:
        msg += f"  ({detail})"
    print(msg)
    results.append((ok, label, detail))

def main():
    # Load SA key
    with open(SA_KEY_PATH) as f:
        sa = json.load(f)
    print(f"🔑 Loaded service account: {sa['client_email']}")
    print(f"👤 Impersonating: {IMPERSONATE}\n")

    token = get_access_token(sa)
    print("✅ OAuth token obtained\n")

    # ── 1. Find T4As & Payments folder under Contractors ──────────────────────
    print("🔍 Looking for 'T4As & Payments' under Contractors...")
    t4a_files = list_files(
        token,
        f"name = 'T4As & Payments' and mimeType = 'application/vnd.google-apps.folder' and '{CONTRACTORS_ROOT}' in parents and trashed = false"
    )
    if t4a_files:
        T4A_FOLDER = t4a_files[0]["id"]
        print(f"   Found: {T4A_FOLDER} ({t4a_files[0]['name']})\n")
    else:
        T4A_FOLDER = None
        print("   ⚠️  T4As & Payments folder not found — Helen stmt will be skipped\n")

    # Update Helen stmt destination
    global FILES_TO_MOVE
    updated = []
    for item in FILES_TO_MOVE:
        fid, dest, label = item
        if dest is None:
            if T4A_FOLDER:
                updated.append((fid, T4A_FOLDER, label))
            else:
                updated.append((fid, None, label + " [SKIPPED - no T4A folder]"))
        else:
            updated.append(item)
    FILES_TO_MOVE[:] = updated

    # ── 2. Find duplicate Bookkeeping folder under Finances root ──────────────
    print("🔍 Looking for duplicate Bookkeeping folder under Finances root...")
    bk_files = list_files(
        token,
        f"name = 'Bookkeeping' and mimeType = 'application/vnd.google-apps.folder' and '{FINANCES_ROOT}' in parents and trashed = false"
    )
    dup_bk = [f for f in bk_files if f["id"] != KEEP_BOOKKEEPING_ID]
    if dup_bk:
        dup_id = dup_bk[0]["id"]
        print(f"   Found duplicate: {dup_id}")
        # Check if empty
        children = list_files(token, f"'{dup_id}' in parents and trashed = false")
        if not children:
            print(f"   Duplicate is empty — trashing it...")
            try:
                trash_file(token, dup_id)
                log(True, f"Duplicate Bookkeeping folder trashed", dup_id)
            except Exception as e:
                log(False, f"Duplicate Bookkeeping folder trash failed", str(e))
        else:
            print(f"   ⚠️  Duplicate has {len(children)} children — NOT trashing (needs manual review)")
            log(False, "Duplicate Bookkeeping NOT trashed (not empty)", f"{len(children)} children")
    else:
        print("   No duplicate Bookkeeping folder found (or only the keep one exists)\n")
    print()

    # ── 3. Move files ──────────────────────────────────────────────────────────
    print("📁 Moving files...\n")
    for fid, dest_folder, label in FILES_TO_MOVE:
        if dest_folder is None:
            log(False, label, "skipped — destination not resolved")
            continue
        try:
            # Get current parents
            meta = get_file_parents(token, fid)
            current_parents = meta.get("parents", [])
            # We always try to remove from NMC_ROOT; if not in there, just add to dest
            remove_from = NMC_ROOT if NMC_ROOT in current_parents else (current_parents[0] if current_parents else NMC_ROOT)
            result = drive_patch(token, fid, dest_folder, remove_from)
            log(True, label, f"id={fid}")
        except Exception as e:
            log(False, label, str(e)[:120])
        time.sleep(0.3)

    # ── 4. Move folders ────────────────────────────────────────────────────────
    print("\n📂 Moving folders...\n")
    for fid, dest_folder, label in FOLDERS_TO_MOVE:
        try:
            meta = get_file_parents(token, fid)
            current_parents = meta.get("parents", [])
            remove_from = NMC_ROOT if NMC_ROOT in current_parents else (current_parents[0] if current_parents else NMC_ROOT)
            result = drive_patch(token, fid, dest_folder, remove_from)
            log(True, label, f"id={fid}")
        except Exception as e:
            log(False, label, str(e)[:120])
        time.sleep(0.3)

    # ── 5. Write summary ───────────────────────────────────────────────────────
    summary_path = "/Users/harvey/.openclaw/workspace/memory/drive-cleanup-2026-03-28.md"
    os.makedirs(os.path.dirname(summary_path), exist_ok=True)

    successes = [r for r in results if r[0]]
    failures = [r for r in results if not r[0]]

    summary = f"""# Google Drive Cleanup — 2026-03-28

**Run by:** Harvey (subagent)
**Impersonating:** {IMPERSONATE}
**NMC Root:** {NMC_ROOT}

## Summary
- ✅ Succeeded: {len(successes)}
- ❌ Failed: {len(failures)}

## Moves Completed

| Item | Result | Detail |
|------|--------|--------|
"""
    for ok, label, detail in results:
        mark = "✅" if ok else "❌"
        summary += f"| {label} | {mark} | {detail} |\n"

    summary += """
## Destination Reference

| Folder | ID |
|--------|----|
| Strategy | 1C0Mqa6YVA0xdDZo8fRnSiFPl3Pjl0Mos |
| Finance/Budgets & Forecasts | 1YVr8wmwGWgPnKC8Nvs1RPqf_BD6EQnkV |
| Finance/Bookkeeping (keep) | 10nts9dTNb4Z11aem9eUmkxX0rejPz5uw |
| Operations/SOPs | 1aDJkGPdyJaNBYwN_XR8RBlljfau9v-C6 |
| HR (root) | 16PC5goXad69KeSlrRGxbpU52tFSao3P4 |
| Contractors root | 1b-BAiWC5MhRZk15kdCKb6XqLAscb92qc |
| Marketing (root) | 16rJSPO3vtxWC6rkrxPqPL642jRo87glL |
| Operations (root) | 1_X33NEBI9npmn_BpruIg6m4IwOeMHQd3 |
| Archive | 1C973EXxXHjY3tCIN3mnWV5cI8gUgy5vl |
"""

    with open(summary_path, "w") as f:
        f.write(summary)

    print(f"\n📝 Summary written to: {summary_path}")
    print(f"\n{'='*60}")
    print(f"DONE — ✅ {len(successes)} succeeded, ❌ {len(failures)} failed")
    if failures:
        print("\nFailed items:")
        for _, label, detail in failures:
            print(f"  ❌ {label}: {detail}")

if __name__ == "__main__":
    main()
