#!/usr/bin/env python3
"""CLI wrapper around superpos_agent_core.SuperposClient workflow methods.

Subcommands mirror the client API:
  list, show, create, update, delete,
  versions, version, diff, rollback,
  runs, run-show, start, cancel, retry

Prints JSON to stdout, errors to stderr.  Designed for the LLM to call
via Bash and pipe through ``jq`` for follow-up queries.
"""

from __future__ import annotations

import argparse
import asyncio
import json
import os
import sys
from typing import Any

# ``superpos_agent_core`` is pip-installed via the container's requirements.txt.
from superpos_agent_core import BaseConfig, SuperposClient


def _config_from_env() -> BaseConfig:
    base_url = os.environ.get("SUPERPOS_BASE_URL", "").rstrip("/")
    hive_id = os.environ.get("SUPERPOS_HIVE_ID", "")
    agent_id = os.environ.get("SUPERPOS_AGENT_ID", "")
    token = os.environ.get("SUPERPOS_API_TOKEN", "")
    refresh = os.environ.get("SUPERPOS_REFRESH_TOKEN", "")

    if not (base_url and hive_id and token):
        print(
            "Error: SUPERPOS_BASE_URL, SUPERPOS_HIVE_ID, and "
            "SUPERPOS_API_TOKEN must be set in the environment.",
            file=sys.stderr,
        )
        sys.exit(2)

    return BaseConfig(
        superpos_base_url=base_url,
        superpos_hive_id=hive_id,
        superpos_agent_id=agent_id,
        superpos_api_token=token,
        superpos_refresh_token=refresh,
    )


def _print(value: Any) -> None:
    """Print a JSON-serialisable value, indented for human reading."""
    print(json.dumps(value, indent=2, default=str))


def _load_json_arg(label: str, raw: str | None) -> Any:
    """Parse a CLI flag value as JSON.

    Supports ``@path/to/file.json`` to read from disk — useful for
    multi-step ``steps`` definitions that would be unwieldy on a single
    shell line.  Anything else is parsed as a literal JSON string.
    """
    if raw is None:
        return None
    if raw.startswith("@"):
        path = raw[1:]
        try:
            with open(path, encoding="utf-8") as f:
                text = f.read()
        except OSError as e:
            print(f"Error: --{label} could not read file {path!r} ({e})",
                  file=sys.stderr)
            sys.exit(2)
    else:
        text = raw
    try:
        return json.loads(text)
    except json.JSONDecodeError as e:
        print(f"Error: --{label} must be valid JSON ({e})", file=sys.stderr)
        sys.exit(2)


async def _run(args: argparse.Namespace) -> int:
    config = _config_from_env()
    client = SuperposClient(config)
    try:
        if args.cmd == "list":
            _print(await client.list_workflows(
                is_active=args.active,
                search=args.q,
                page=args.page,
                per_page=args.per_page,
            ))

        elif args.cmd == "show":
            _print(await client.get_workflow(args.workflow))

        elif args.cmd == "create":
            trigger = _load_json_arg("trigger-config", args.trigger_config)
            steps = _load_json_arg("steps", args.steps)
            settings = _load_json_arg("settings", args.settings)
            _print(await client.create_workflow(
                name=args.name,
                slug=args.slug,
                trigger_config=trigger,
                steps=steps,
                description=args.description,
                settings=settings,
                is_active=not args.inactive,
            ))

        elif args.cmd == "update":
            trigger = _load_json_arg("trigger-config", args.trigger_config)
            steps = _load_json_arg("steps", args.steps)
            settings = _load_json_arg("settings", args.settings)
            is_active: bool | None = None
            if args.activate:
                is_active = True
            elif args.inactive:
                is_active = False
            try:
                _print(await client.update_workflow(
                    args.workflow,
                    name=args.name,
                    slug=args.slug,
                    trigger_config=trigger,
                    steps=steps,
                    description=args.description,
                    settings=settings,
                    is_active=is_active,
                ))
            except ValueError as e:
                print(f"Error: {e}", file=sys.stderr)
                return 2

        elif args.cmd == "delete":
            await client.delete_workflow(args.workflow)
            _print({"ok": True})

        elif args.cmd == "versions":
            _print(await client.list_workflow_versions(args.workflow))

        elif args.cmd == "version":
            _print(await client.get_workflow_version(args.workflow, args.n))

        elif args.cmd == "diff":
            _print(await client.diff_workflow_versions(
                args.workflow, args.from_version, args.to_version,
            ))

        elif args.cmd == "rollback":
            _print(await client.rollback_workflow_version(args.workflow, args.n))

        elif args.cmd == "runs":
            _print(await client.list_workflow_runs(
                args.workflow,
                status=args.status,
                page=args.page,
                per_page=args.per_page,
            ))

        elif args.cmd == "run-show":
            _print(await client.get_workflow_run(args.workflow, args.run_id))

        elif args.cmd == "start":
            payload = _load_json_arg("payload", args.payload)
            _print(await client.start_workflow_run(
                args.workflow, payload=payload,
            ))

        elif args.cmd == "cancel":
            _print(await client.cancel_workflow_run(args.workflow, args.run_id))

        elif args.cmd == "retry":
            _print(await client.retry_workflow_run(args.workflow, args.run_id))

        else:
            print(f"Unknown subcommand: {args.cmd}", file=sys.stderr)
            return 2

        return 0
    finally:
        await client.close()


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="superpos-workflows",
        description=(
            "Manage Superpos workflows: list, show, create, update, delete, "
            "inspect versions / diffs, and start / cancel / retry runs."
        ),
    )
    sub = parser.add_subparsers(dest="cmd", required=True)

    # list
    p_list = sub.add_parser("list", help="Paginated list with filters")
    p_list.add_argument(
        "--active", dest="active", action="store_true", default=None,
        help="Only return active workflows (server param: is_active=true)",
    )
    p_list.add_argument(
        "--q",
        help="Case-insensitive substring against name/slug "
             "(server param: search)",
    )
    p_list.add_argument("--page", type=int, help="1-based page index")
    p_list.add_argument("--per-page", type=int, help="Page size (server caps at 100)")

    # show
    p_show = sub.add_parser("show", help="Fetch a single workflow by ID or slug")
    p_show.add_argument("workflow")

    # create
    p_create = sub.add_parser("create", help="Create a new workflow definition (v1)")
    p_create.add_argument("--name", required=True)
    p_create.add_argument(
        "--slug", required=True,
        help="URL-safe slug, ^[a-z0-9]+(?:-[a-z0-9]+)*$",
    )
    p_create.add_argument(
        "--trigger-config", required=True,
        help="JSON object or @path/to/file.json",
    )
    p_create.add_argument(
        "--steps", required=True,
        help="JSON object keyed by step name (or @path/to/file.json). "
             "Example: {\"plan\": {...}, \"build\": {...}}",
    )
    p_create.add_argument("--description")
    p_create.add_argument(
        "--settings", help="JSON object or @path/to/file.json",
    )
    p_create.add_argument(
        "--inactive", action="store_true",
        help="Create the workflow disabled (is_active=false)",
    )

    # update
    p_update = sub.add_parser(
        "update", help="Partial update; supplies become the next workflow version",
    )
    p_update.add_argument("workflow")
    p_update.add_argument("--name")
    p_update.add_argument("--slug")
    p_update.add_argument(
        "--trigger-config", help="JSON object or @path/to/file.json",
    )
    p_update.add_argument(
        "--steps",
        help="JSON object keyed by step name, or @path/to/file.json",
    )
    p_update.add_argument("--description")
    p_update.add_argument(
        "--settings", help="JSON object or @path/to/file.json",
    )
    activity = p_update.add_mutually_exclusive_group()
    activity.add_argument(
        "--activate", action="store_true", help="Set is_active=true",
    )
    activity.add_argument(
        "--inactive", action="store_true", help="Set is_active=false",
    )

    # delete
    p_delete = sub.add_parser("delete", help="Delete a workflow (must have no active runs)")
    p_delete.add_argument("workflow")

    # versions
    p_versions = sub.add_parser("versions", help="List version history")
    p_versions.add_argument("workflow")

    # version
    p_version = sub.add_parser("version", help="Fetch one immutable version snapshot")
    p_version.add_argument("workflow")
    p_version.add_argument("n", type=int, help="Version number")

    # diff
    p_diff = sub.add_parser("diff", help="Structural diff between two versions")
    p_diff.add_argument("workflow")
    p_diff.add_argument("from_version", type=int, metavar="from")
    p_diff.add_argument("to_version", type=int, metavar="to")

    # rollback
    p_rollback = sub.add_parser(
        "rollback", help="Restore an older version as the new head",
    )
    p_rollback.add_argument("workflow")
    p_rollback.add_argument("n", type=int, help="Version number to restore")

    # runs
    p_runs = sub.add_parser("runs", help="Paginated runs index for a workflow")
    p_runs.add_argument("workflow")
    p_runs.add_argument(
        "--status",
        help="Filter by run status (running, completed, failed, cancelled, retrying, retried)",
    )
    p_runs.add_argument("--page", type=int)
    p_runs.add_argument("--per-page", type=int)

    # run-show
    p_run_show = sub.add_parser(
        "run-show", help="Fetch a single run with thread and step_states",
    )
    p_run_show.add_argument("workflow")
    p_run_show.add_argument("run_id")

    # start
    p_start = sub.add_parser("start", help="Kick off a new workflow run")
    p_start.add_argument("workflow")
    p_start.add_argument(
        "--payload", help="JSON object or @path/to/file.json",
    )

    # cancel
    p_cancel = sub.add_parser("cancel", help="Cancel a running workflow run")
    p_cancel.add_argument("workflow")
    p_cancel.add_argument("run_id")

    # retry
    p_retry = sub.add_parser("retry", help="Re-run a failed workflow run")
    p_retry.add_argument("workflow")
    p_retry.add_argument("run_id")

    return parser


def main(argv: list[str] | None = None) -> int:
    args = _build_parser().parse_args(argv)
    return asyncio.run(_run(args))


if __name__ == "__main__":
    sys.exit(main())
