#!/usr/bin/env python3
"""AI Notepad CLI — manage notes from the terminal."""

import argparse
import json
import os
import secrets
import string
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone

SUPABASE_URL = "https://xryosrxuoqwohyqxszhs.supabase.co"
SUPABASE_ANON_KEY = (
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
    "eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhyeW9zcnh1b3F3b2h5cXhzemhzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAyMjc4NTgsImV4cCI6MjA4NTgwMzg1OH0."
    "3S_bdjjTIX5-Ex8z7ShazQNzF5DuyE6iaWsWwZa5XQk"
)
CONFIG_DIR = os.path.expanduser("~/.ai-notepad")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
SESSION_FILE = os.path.join(CONFIG_DIR, "session.json")
PUBLIC_BASE = "https://hugozhu.site/notes"


def load_config():
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE) as f:
            return json.load(f)
    return {"anon_key": SUPABASE_ANON_KEY}


def get_anon_key():
    return load_config().get("anon_key", SUPABASE_ANON_KEY)


def api(path, method="GET", body=None, token=None, prefer=None):
    """Make a Supabase REST API call."""
    anon_key = get_anon_key()
    url = f"{SUPABASE_URL}{path}"
    headers = {"apikey": anon_key, "Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    if prefer:
        headers["Prefer"] = prefer
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            raw = resp.read().decode()
            return json.loads(raw) if raw else None
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
        try:
            err_body = json.loads(err_body)
        except Exception:
            pass
        if e.code == 401:
            print("Session expired. Run: ai-notepad login", file=sys.stderr)
            sys.exit(1)
        print(f"Error {e.code}: {err_body}", file=sys.stderr)
        sys.exit(1)


def get_token():
    if not os.path.exists(SESSION_FILE):
        print("Not logged in. Run: ai-notepad login", file=sys.stderr)
        sys.exit(1)
    with open(SESSION_FILE) as f:
        session = json.load(f)
    # Check expiry
    if session.get("expires_at") and time.time() > session["expires_at"]:
        print("Session expired. Run: ai-notepad login", file=sys.stderr)
        sys.exit(1)
    return session["access_token"]


def cmd_setup(args):
    """Save Supabase anon key."""
    os.makedirs(CONFIG_DIR, exist_ok=True)
    with open(CONFIG_FILE, "w") as f:
        json.dump({"anon_key": args.anon_key}, f)
    os.chmod(CONFIG_FILE, 0o600)
    print(f"Config saved to {CONFIG_FILE}")


def cmd_login(args):
    """Authenticate with email/password or paste JWT token."""
    if args.token:
        # Direct token login (e.g. from browser DevTools)
        os.makedirs(CONFIG_DIR, exist_ok=True)
        # Fetch user info to store user_id and email
        anon_key = get_anon_key()
        req = urllib.request.Request(
            f"{SUPABASE_URL}/auth/v1/user",
            headers={
                "apikey": anon_key,
                "Authorization": f"Bearer {args.token}",
                "Content-Type": "application/json",
            },
        )
        try:
            with urllib.request.urlopen(req, timeout=15) as resp:
                user_info = json.loads(resp.read())
        except urllib.error.HTTPError as e:
            print(f"Invalid token: {e.code}", file=sys.stderr)
            sys.exit(1)
        with open(SESSION_FILE, "w") as f:
            json.dump(
                {
                    "access_token": args.token,
                    "user": user_info["email"],
                    "user_id": user_info["id"],
                    "expires_at": time.time() + 3600,
                },
                f,
            )
        os.chmod(SESSION_FILE, 0o600)
        print(f"Logged in as {user_info['email']}")
        return

    email = args.email or input("Email: ")
    password = args.password or input("Password: ")
    result = api(
        "/auth/v1/token?grant_type=password",
        "POST",
        {"email": email, "password": password},
    )
    os.makedirs(CONFIG_DIR, exist_ok=True)
    with open(SESSION_FILE, "w") as f:
        json.dump(
            {
                "access_token": result["access_token"],
                "user": result["user"]["email"],
                "user_id": result["user"]["id"],
                "expires_at": result.get("expires_at", time.time() + 3600),
            },
            f,
        )
    os.chmod(SESSION_FILE, 0o600)
    print(f"Logged in as {result['user']['email']}")


def cmd_register(args):
    """Register a new account with email and password."""
    allowed_domains = ["dingtalk.com", "taobao.com", "alibaba-inc.com"]
    email = args.email or input("Email: ")
    domain = email.rsplit("@", 1)[-1].lower() if "@" in email else ""
    if domain not in allowed_domains:
        print(
            f"Error: email domain must be one of: {', '.join(allowed_domains)}",
            file=sys.stderr,
        )
        sys.exit(1)
    password = args.password or input("Password (min 8 chars): ")
    if len(password) < 8:
        print("Error: password must be at least 8 characters", file=sys.stderr)
        sys.exit(1)
    anon_key = get_anon_key()
    url = f"{SUPABASE_URL}/functions/v1/register"
    headers = {
        "apikey": anon_key,
        "Authorization": f"Bearer {anon_key}",
        "Content-Type": "application/json",
    }
    data = json.dumps({"email": email, "password": password}).encode()
    req = urllib.request.Request(url, data=data, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            result = json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
        try:
            err_body = json.loads(err_body)
            msg = err_body.get("error", err_body)
        except Exception:
            msg = err_body
        print(f"Registration failed: {msg}", file=sys.stderr)
        sys.exit(1)
    if result.get("access_token"):
        os.makedirs(CONFIG_DIR, exist_ok=True)
        with open(SESSION_FILE, "w") as f:
            json.dump(
                {
                    "access_token": result["access_token"],
                    "user": result["user"]["email"],
                    "user_id": result["user"]["id"],
                    "expires_at": result.get("expires_at", time.time() + 3600),
                },
                f,
            )
        os.chmod(SESSION_FILE, 0o600)
        print(f"Registered and logged in as {email}")
    else:
        print(f"Registered: {email}")
        print("Please log in manually: ai-notepad login -e {email} -p ***")


def cmd_logout(args):
    """Clear session."""
    if os.path.exists(SESSION_FILE):
        os.remove(SESSION_FILE)
    print("Logged out")


def cmd_whoami(args):
    """Show current user."""
    if not os.path.exists(SESSION_FILE):
        print("Not logged in")
        return
    with open(SESSION_FILE) as f:
        session = json.load(f)
    print(f"Logged in as {session['user']}")


def cmd_list(args):
    """List recent notes."""
    token = get_token()
    limit = args.limit or 10
    offset = args.offset or 0
    notes = api(
        f"/rest/v1/notes?select=id,title,publish_slug,updated_at"
        f"&order=updated_at.desc&offset={offset}&limit={limit}",
        token=token,
    )
    if not notes:
        print("No notes found")
        return
    for n in notes:
        pub = " 📢" if n.get("publish_slug") else ""
        date = n["updated_at"][:16].replace("T", " ")
        title = (n["title"] or "Untitled")[:50]
        print(f"  {n['id'][:8]}  {date}  {title}{pub}")


def cmd_get(args):
    """Get a note by ID (prefix match)."""
    token = get_token()
    full_id = resolve_id(args.id, token)
    notes = api(
        f"/rest/v1/notes?id=eq.{full_id}"
        f"&select=id,title,content,updated_at,publish_slug,published_at&limit=1",
        token=token,
    )
    if not notes:
        print("Note not found", file=sys.stderr)
        sys.exit(1)
    n = notes[0]
    print(f"# {n['title']}")
    print(f"\nID: {n['id']}")
    print(f"Updated: {n['updated_at'][:19]}")
    if n.get("publish_slug"):
        print(f"Published: {PUBLIC_BASE}/p/{n['publish_slug']}")
    print(f"\n{n['content']}")


import re as _re


def resolve_id(note_id, token):
    """Resolve a note ID (prefix or full UUID) to the full UUID."""
    if _re.match(
        r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
        note_id,
    ):
        return note_id
    # Prefix match: fetch recent and filter
    all_notes = api(
        f"/rest/v1/notes?select=id&order=updated_at.desc&limit=100",
        token=token,
    )
    matches = [n["id"] for n in (all_notes or []) if n["id"].startswith(note_id)]
    if not matches:
        print(f"Note not found: {note_id}", file=sys.stderr)
        sys.exit(1)
    if len(matches) > 1:
        print(f"Ambiguous prefix '{note_id}', matches: {', '.join(m[:8] for m in matches)}", file=sys.stderr)
        sys.exit(1)
    return matches[0]


def get_user_id():
    """Get user_id from session, fetching if missing."""
    with open(SESSION_FILE) as f:
        session = json.load(f)
    if "user_id" in session:
        return session["user_id"]
    # Fallback: fetch from auth endpoint
    token = get_token()
    result = api("/auth/v1/user", token=token)
    user_id = result["id"]
    # Cache it
    session["user_id"] = user_id
    session["user"] = result.get("email", session.get("user", ""))
    with open(SESSION_FILE, "w") as f:
        json.dump(session, f)
    os.chmod(SESSION_FILE, 0o600)
    return user_id


def cmd_create(args):
    """Create a new note."""
    token = get_token()
    title = args.title
    content = args.content or ""
    if args.file:
        with open(args.file) as f:
            content = f.read()
    user_id = get_user_id()
    result = api(
        "/rest/v1/notes",
        "POST",
        {"title": title, "content": content, "user_id": user_id},
        token=token,
        prefer="return=representation",
    )
    if result and len(result) > 0:
        print(f"Created: {result[0]['id'][:8]} — {title}")
    else:
        print(f"Created: {title}")


def cmd_update(args):
    """Update a note's title and/or content."""
    token = get_token()
    full_id = resolve_id(args.id, token)
    updates = {}
    if args.title:
        updates["title"] = args.title
    if args.content is not None:
        updates["content"] = args.content
    if args.file:
        with open(args.file) as f:
            updates["content"] = f.read()
    if not updates:
        print("Nothing to update (use --title, --content, or --file)", file=sys.stderr)
        sys.exit(1)
    api(f"/rest/v1/notes?id=eq.{full_id}", "PATCH", updates, token=token)
    print(f"Updated {full_id[:8]}")


def cmd_delete(args):
    """Delete a note."""
    token = get_token()
    full_id = resolve_id(args.id, token)
    api(f"/rest/v1/notes?id=eq.{full_id}", "DELETE", token=token)
    print(f"Deleted {full_id[:8]}")


def cmd_search(args):
    """Search notes by keyword."""
    token = get_token()
    q = urllib.parse.quote(args.query)
    notes = api(
        f"/rest/v1/notes?or=(title.ilike.%25{q}%25,content.ilike.%25{q}%25)"
        f"&select=id,title,updated_at&order=updated_at.desc&limit=20",
        token=token,
    )
    if not notes:
        print("No results")
        return
    for n in notes:
        date = n["updated_at"][:16].replace("T", " ")
        title = (n["title"] or "Untitled")[:60]
        print(f"  {n['id'][:8]}  {date}  {title}")


def generate_slug():
    chars = "".join(c for c in string.ascii_letters + string.digits if c not in "0O1lI")
    return "".join(secrets.choice(chars) for _ in range(8))


def cmd_publish(args):
    """Publish a note as a public page."""
    token = get_token()
    full_id = resolve_id(args.id, token)
    slug = generate_slug()
    now = datetime.now(timezone.utc).isoformat()
    api(
        f"/rest/v1/notes?id=eq.{full_id}",
        "PATCH",
        {"publish_slug": slug, "published_at": now},
        token=token,
    )
    url = f"{PUBLIC_BASE}/p/{slug}"
    print(f"Published: {url}")


def cmd_unpublish(args):
    """Unpublish a note."""
    token = get_token()
    full_id = resolve_id(args.id, token)
    api(
        f"/rest/v1/notes?id=eq.{full_id}",
        "PATCH",
        {"publish_slug": None, "published_at": None},
        token=token,
    )
    print("Unpublished")


def cmd_annotations(args):
    """List annotations for a note."""
    token = get_token()
    full_id = resolve_id(args.id, token)
    annotations = api(
        f"/rest/v1/note_annotations?note_id=eq.{full_id}"
        f"&select=id,selected_text,comment,visitor_name,created_at"
        f"&order=created_at.desc&limit=50",
        token=token,
    )
    if not annotations:
        print("No annotations")
        return
    print(f"Annotations for {full_id[:8]}:")
    print()
    for a in annotations:
        name = a.get("visitor_name") or "Anonymous"
        text = a["selected_text"][:60]
        comment = (a.get("comment") or "")[:80]
        date = a["created_at"][:16].replace("T", " ")
        print(f"  {a['id'][:8]}  {date}  [{name}]")
        print(f"    「{text}」")
        if comment:
            print(f"    → {comment}")
        print()


def cmd_annotation_delete(args):
    """Delete a specific annotation."""
    token = get_token()
    api(f"/rest/v1/note_annotations?id=eq.{args.id}", "DELETE", token=token)
    print(f"Deleted annotation {args.id[:8]}")


def main():
    parser = argparse.ArgumentParser(
        prog="ai-notepad",
        description="AI Notepad CLI — manage notes from the terminal",
    )
    sub = parser.add_subparsers(dest="command")

    # setup
    p = sub.add_parser("setup", help="Configure anon key (one-time)")
    p.add_argument("--anon-key", required=True, help="Supabase anon key")

    # login
    p = sub.add_parser("login", help="Authenticate with email/password or token")
    p.add_argument("--email", "-e")
    p.add_argument("--password", "-p")
    p.add_argument("--token", "-t", help="Paste JWT access token directly")

    # register
    p = sub.add_parser("register", help="Register a new account with email and password")
    p.add_argument("--email", "-e")
    p.add_argument("--password", "-p")

    # logout
    sub.add_parser("logout", help="Clear session")

    # whoami
    sub.add_parser("whoami", help="Show current user")

    # list
    p = sub.add_parser("list", aliases=["ls"], help="List notes")
    p.add_argument("--limit", "-n", type=int, default=10)
    p.add_argument("--offset", type=int, default=0)

    # get
    p = sub.add_parser("get", help="Show a note (ID prefix OK)")
    p.add_argument("id")

    # create
    p = sub.add_parser("create", help="Create a note")
    p.add_argument("title")
    p.add_argument("--content", "-c", default="")
    p.add_argument("--file", "-f", help="Read content from file")

    # update
    p = sub.add_parser("update", help="Update a note")
    p.add_argument("id")
    p.add_argument("--title", "-t")
    p.add_argument("--content", "-c")
    p.add_argument("--file", "-f")

    # delete
    p = sub.add_parser("delete", help="Delete a note")
    p.add_argument("id")

    # search
    p = sub.add_parser("search", help="Search notes")
    p.add_argument("query")

    # publish
    p = sub.add_parser("publish", help="Publish note as public page")
    p.add_argument("id")

    # unpublish
    p = sub.add_parser("unpublish", help="Unpublish a note")
    p.add_argument("id")

    # annotations
    p = sub.add_parser("annotations", help="List annotations for a note")
    p.add_argument("id", help="Note ID (prefix OK)")

    # annotation-delete
    p = sub.add_parser("annotation-delete", help="Delete a specific annotation")
    p.add_argument("id", help="Annotation ID (full UUID)")

    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        sys.exit(1)

    cmds = {
        "setup": cmd_setup,
        "login": cmd_login,
        "register": cmd_register,
        "logout": cmd_logout,
        "whoami": cmd_whoami,
        "list": cmd_list,
        "ls": cmd_list,
        "get": cmd_get,
        "create": cmd_create,
        "update": cmd_update,
        "delete": cmd_delete,
        "search": cmd_search,
        "publish": cmd_publish,
        "unpublish": cmd_unpublish,
        "annotations": cmd_annotations,
        "annotation-delete": cmd_annotation_delete,
    }
    cmds[args.command](args)


if __name__ == "__main__":
    main()
