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

Subcommands mirror the client API:
  list, get, create, patch,
  link-issue, add-issue, unlink-issue, list-issues

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

State transitions (``POST /tracks/{slug}/transition``) are intentionally
not exposed here — see the SKILL.md for the state-machine mapping and
the dashboard editor at ``/hives/{hive}/tracks/{slug}``.
"""

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


# Mirror Track::STATES on the server (app/Models/Track.php).
_TRACK_STATES = frozenset({"planning", "active", "paused", "done", "archived"})


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 _read_spec_file(path: str | None) -> str | None:
    if path is None:
        return None
    try:
        with open(path, "r", encoding="utf-8") as fh:
            return fh.read()
    except OSError as e:
        print(f"Error: cannot read --spec-file {path!r}: {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_tracks(status=args.status))

        elif args.cmd == "get":
            _print(await client.get_track_by_slug(args.slug))

        elif args.cmd == "create":
            spec = _read_spec_file(args.spec_file)
            _print(await client.create_track(
                slug=args.slug,
                name=args.title,
                description=args.description,
                spec=spec,
                state=args.status,
            ))

        elif args.cmd == "patch":
            spec = _read_spec_file(args.spec_file)
            kwargs = {}
            if args.title is not None:
                kwargs["name"] = args.title
            if args.description is not None:
                kwargs["description"] = args.description
            if spec is not None:
                kwargs["spec"] = spec
            if not kwargs:
                print(
                    "Error: patch requires at least one of --title, "
                    "--description, or --spec-file.",
                    file=sys.stderr,
                )
                return 2
            _print(await client.patch_track(args.slug, **kwargs))

        elif args.cmd in ("link-issue", "add-issue"):
            _print(await client.link_track_issue(args.track_slug, args.issue_id))

        elif args.cmd == "unlink-issue":
            await client.unlink_track_issue(args.track_slug, args.issue_id)
            _print({"ok": True, "track_slug": args.track_slug, "issue_id": args.issue_id})

        elif args.cmd == "list-issues":
            kwargs: dict[str, Any] = {}
            if args.page is not None:
                kwargs["page"] = args.page
            if args.per_page is not None:
                kwargs["per_page"] = args.per_page
            _print(await client.list_track_issues(args.slug, **kwargs))

        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-tracks",
        description=(
            "Manage Superpos tracks — list/get/create/patch, and link or "
            "unlink issues to a track. See SKILL.md for the bootstrap "
            "runbook (proposal → track → issues)."
        ),
    )
    sub = parser.add_subparsers(dest="cmd", required=True)

    # list
    p_list = sub.add_parser("list", help="List tracks in the hive")
    p_list.add_argument(
        "--status", choices=sorted(_TRACK_STATES),
        help="Filter by track state (enforced client-side)",
    )

    # get
    p_get = sub.add_parser("get", help="Fetch a single track by slug (includes spec)")
    p_get.add_argument("slug", help="Track slug, e.g. 'agent-capabilities'")

    # create
    p_create = sub.add_parser(
        "create", help="Create a new track (slug is immutable)",
    )
    p_create.add_argument(
        "--slug", required=True,
        help="Stable slug (1-100 chars, lowercase alphanumeric + hyphens)",
    )
    p_create.add_argument("--title", required=True, help="Track display name (1-255 chars)")
    p_create.add_argument(
        "--description",
        help="Free-text description (max 10000 chars)",
    )
    p_create.add_argument(
        "--spec-file",
        help="Path to a file containing the markdown spec (max 200000 chars)",
    )
    p_create.add_argument(
        "--status", choices=sorted(_TRACK_STATES),
        help="Initial state (default: planning)",
    )

    # patch
    p_patch = sub.add_parser(
        "patch", help="Update title/description/spec; slug and state are not patchable",
    )
    p_patch.add_argument("slug", help="Track slug to update")
    p_patch.add_argument("--title", help="New display name")
    p_patch.add_argument("--description", help="New description (empty string to clear)")
    p_patch.add_argument("--spec-file", help="Path to a file containing the new spec body")

    # link-issue / add-issue (alias)
    p_link = sub.add_parser(
        "link-issue", help="Link an existing issue to a track by slug",
    )
    p_link.add_argument("track_slug", help="Track slug to link to")
    p_link.add_argument("issue_id", help="Issue ULID to link")

    p_add = sub.add_parser(
        "add-issue", help="Alias for `link-issue` (discoverability)",
    )
    p_add.add_argument("track_slug", help="Track slug to link to")
    p_add.add_argument("issue_id", help="Issue ULID to link")

    # unlink-issue
    p_unlink = sub.add_parser(
        "unlink-issue", help="Unlink an issue from a track by slug",
    )
    p_unlink.add_argument("track_slug", help="Track slug to unlink from")
    p_unlink.add_argument("issue_id", help="Issue ULID to unlink")

    # list-issues
    p_list_issues = sub.add_parser(
        "list-issues", help="List issues linked to a track (paginated envelope)",
    )
    p_list_issues.add_argument("slug", help="Track slug, e.g. 'k1'")
    p_list_issues.add_argument(
        "--page", type=int,
        help="1-based page index (forwarded as ?page=; see meta.current_page)",
    )
    p_list_issues.add_argument(
        "--per-page", type=int,
        help="Page size (forwarded as ?per_page=; server caps at 100)",
    )

    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())
