#!/usr/bin/env python3
"""odysseus-webhook — shell wrapper for scheduled-task webhook tokens.

Tasks in the scheduled-task system can carry a `webhook_token`. Any
HTTP POST to `/api/webhook/<token>` fires the task. This CLI lists,
rotates, and revokes those tokens.

    odysseus-webhook list                       # tasks that have a token
    odysseus-webhook show TASK_ID
    odysseus-webhook rotate TASK_ID             # generate a fresh token
    odysseus-webhook revoke TASK_ID             # remove the token
    odysseus-webhook url TASK_ID --base https://app.example.com
"""

from __future__ import annotations
import sys
import os, sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "_lib"))
from cli import quiet_logs, emit, fail, common_parser, run, REPO_ROOT as _REPO_ROOT
quiet_logs()

import argparse, json, logging, os, secrets, sys
from pathlib import Path

try:
    from core.database import SessionLocal, ScheduledTask
    quiet_logs()
except ModuleNotFoundError as e:
    sys.stderr.write(f"error: {e}\nhint: run from repo root with venv active.\n")
    sys.exit(2)


def _mask_token(token: str, reveal: bool = False) -> str:
    token = token or ""
    if reveal:
        return token
    if not token:
        return ""
    if len(token) <= 10:
        return "***"
    return token[:6] + "…" + token[-4:]


def _summary(t: "ScheduledTask", reveal: bool = False) -> dict:
    tok = t.webhook_token or ""
    return {
        "task_id": t.id,
        "name": t.name,
        "status": t.status,
        "task_type": t.task_type,
        "webhook_token": _mask_token(tok, reveal),
        "has_token": bool(tok),
    }


def cmd_list(args):
    db = SessionLocal()
    try:
        rows = db.query(ScheduledTask).filter(ScheduledTask.webhook_token.isnot(None)).all()
        emit([_summary(t, args.reveal) for t in rows], args)
    finally:
        db.close()


def cmd_show(args):
    db = SessionLocal()
    try:
        t = db.get(ScheduledTask, args.id)
        if not t:
            fail(f"no task with id {args.id!r}")
        emit(_summary(t, args.reveal), args)
    finally:
        db.close()


def cmd_rotate(args):
    db = SessionLocal()
    try:
        t = db.get(ScheduledTask, args.id)
        if not t:
            fail(f"no task with id {args.id!r}")
        # 32 bytes urlsafe → 43-char token, plenty of entropy.
        t.webhook_token = secrets.token_urlsafe(32)
        db.commit()
        emit({"ok": True, "task_id": t.id, "webhook_token": t.webhook_token}, args)
    finally:
        db.close()


def cmd_revoke(args):
    db = SessionLocal()
    try:
        t = db.get(ScheduledTask, args.id)
        if not t:
            fail(f"no task with id {args.id!r}")
        old = t.webhook_token
        t.webhook_token = None
        db.commit()
        emit({"ok": True, "task_id": t.id, "revoked": bool(old)}, args)
    finally:
        db.close()


def cmd_url(args):
    db = SessionLocal()
    try:
        t = db.get(ScheduledTask, args.id)
        if not t:
            fail(f"no task with id {args.id!r}")
        if not t.webhook_token:
            fail(f"task {args.id!r} has no webhook token (rotate one first)")
        base = (args.base or "http://localhost:7000").rstrip("/")
        url = f"{base}/api/webhook/{t.webhook_token}"
        emit({
            "task_id": t.id,
            "name": t.name,
            "url": url,
            "curl": f"curl -X POST {url}",
        }, args)
    finally:
        db.close()


def _build_parser():
    common = argparse.ArgumentParser(add_help=False)
    common.add_argument("--pretty", action="store_true")
    p = argparse.ArgumentParser(prog="odysseus-webhook", parents=[common])
    sub = p.add_subparsers(dest="cmd", required=True)

    pl = sub.add_parser("list", parents=[common])
    pl.add_argument("--reveal", action="store_true", help="show full tokens")
    pl.set_defaults(func=cmd_list)

    psh = sub.add_parser("show", parents=[common])
    psh.add_argument("id")
    psh.add_argument("--reveal", action="store_true")
    psh.set_defaults(func=cmd_show)

    pr = sub.add_parser("rotate", parents=[common])
    pr.add_argument("id")
    pr.set_defaults(func=cmd_rotate)

    prv = sub.add_parser("revoke", parents=[common])
    prv.add_argument("id")
    prv.set_defaults(func=cmd_revoke)

    pu = sub.add_parser("url", parents=[common])
    pu.add_argument("id")
    pu.add_argument("--base", help="base URL (default http://localhost:7000)")
    pu.set_defaults(func=cmd_url)

    return p


if __name__ == "__main__":
    sys.exit(run(_build_parser()))
