#!/usr/bin/env python3
"""
Sync a Markdown file up to a Google Doc for collaboration (§4.5 BPE).

Markdown stays the source of truth; the Doc is a disposable, always-overwritten
mirror people can comment on. Google Drive natively converts `text/markdown` to a
Doc on upload, so headings, lists, bold/italic, tables, and links all carry over —
no HTML/pandoc step needed.

First run creates the Doc and records its id in a sidecar manifest
(`<file>.gdoc.json`) next to the Markdown; later runs update that same Doc in
place, so the share link and comment threads survive. Delete the sidecar to fork
a fresh Doc.

Usage:
    python3 notes/content/notes/resources/sync_markdown_to_gdoc.py PATH/TO/file.md
    python3 .../sync_markdown_to_gdoc.py file.md --folder-id <DRIVE_FOLDER_ID>
    python3 .../sync_markdown_to_gdoc.py file.md --name "Business Process"  # Doc title

Reuses the shared OAuth client (same google_credentials.json as format_sheet.py)
but keeps its own token: this app needs the Drive scope, which the Sheets token
does not carry. The first run opens a browser once to grant Drive access; every
run after that is non-interactive.
"""

from __future__ import annotations

import argparse
import json
import pickle
import sys
from pathlib import Path

from google.auth.exceptions import RefreshError
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload

# drive.file is enough: it grants access only to files this app creates.
SCOPES = ["https://www.googleapis.com/auth/drive.file"]
DOC_MIME = "application/vnd.google-apps.document"

# Secrets live in the central store, private/projects/secrets/resources/ — outside
# the deployable notes/ repo. Personal OAuth client (project ariels-project-440000)
# + a dedicated Drive-scoped token, so Docs are created in — and owned by —
# ariel32lubin@gmail.com. NOTE: this consumer client must be published to
# "In production" in the Google Cloud console, else its refresh token expires
# after 7 days (testing-mode limit) and the first run each week re-prompts.
REPO_ROOT = next(p for p in Path(__file__).resolve().parents if (p / "oyf" / "business").exists())
SECRETS = REPO_ROOT / "private/projects/secrets/resources"
CREDS = SECRETS / "google_credentials.json"
TOKEN = SECRETS / "gdoc_sync_token.pickle"


def authenticate():
    """Return an authenticated Drive service, refreshing/creating the token."""
    creds = None
    if TOKEN.exists():
        with open(TOKEN, "rb") as fh:
            creds = pickle.load(fh)
    if not creds or not creds.valid:
        refreshed = False
        if creds and creds.expired and creds.refresh_token:
            try:
                creds.refresh(Request())
                refreshed = True
            except RefreshError:
                creds = None  # revoked/expired beyond refresh — re-auth below.
        if not refreshed:
            if not CREDS.exists():
                sys.exit(f"Missing OAuth client at {CREDS}")
            creds = InstalledAppFlow.from_client_secrets_file(str(CREDS), SCOPES).run_local_server(port=0)
        with open(TOKEN, "wb") as fh:
            pickle.dump(creds, fh)
    return build("drive", "v3", credentials=creds)


def sync(md_path: Path, folder_id: str | None, name: str | None) -> str:
    """Create or update the mirror Doc for `md_path`; return its web link."""
    service = authenticate()
    title = name or md_path.stem
    sidecar = md_path.with_suffix(md_path.suffix + ".gdoc.json")

    # Drive converts the upload to a Doc when the target mimeType is DOC_MIME.
    media = MediaFileUpload(str(md_path), mimetype="text/markdown", resumable=True)

    doc_id = None
    if sidecar.exists():
        doc_id = json.loads(sidecar.read_text()).get("id")

    if doc_id:
        try:
            file = service.files().update(
                fileId=doc_id, media_body=media, fields="id,webViewLink"
            ).execute()
        except HttpError as e:
            if e.resp.status != 404:
                raise
            doc_id = None  # Doc was deleted upstream — fall through and recreate.

    if not doc_id:
        body = {"name": title, "mimeType": DOC_MIME}
        if folder_id:
            body["parents"] = [folder_id]
        file = service.files().create(
            body=body, media_body=media, fields="id,webViewLink"
        ).execute()
        sidecar.write_text(json.dumps({"id": file["id"]}, indent=2) + "\n")

    return file["webViewLink"]


def main():
    ap = argparse.ArgumentParser(description="Sync a Markdown file to a Google Doc.")
    ap.add_argument("markdown", type=Path, help="path to the .md source of truth")
    ap.add_argument("--folder-id", help="Drive folder to create the Doc in (new Docs only)")
    ap.add_argument("--name", help="Doc title (defaults to the file name)")
    args = ap.parse_args()

    if not args.markdown.exists():
        sys.exit(f"No such file: {args.markdown}")

    link = sync(args.markdown, args.folder_id, args.name)
    print(f"Synced → {link}")


if __name__ == "__main__":
    main()
