"""Backplane command-line surface."""

from __future__ import annotations

import argparse
import asyncio
import json
import os
import tempfile
from pathlib import Path

import uvicorn

from .api import create_app
from .benchmark import run_benchmark_sync
from .contracts import CardManifest, GenerationRequest
from .demo import build_demo_runtime
from .packages import PackageTrustPolicy, build_package, keygen, load_package


def _json(value) -> None:
    print(json.dumps(value, indent=2, sort_keys=True, default=str))


def command_demo(args: argparse.Namespace) -> int:
    runtime = build_demo_runtime(args.state)

    async def run() -> None:
        result = await runtime.run(
            GenerationRequest(tenant_id=args.tenant, prompt=args.prompt),
            authenticate=False,
        )
        _json(result.model_dump(mode="json"))
        await runtime.close()

    asyncio.run(run())
    return 0


def command_serve(args: argparse.Namespace) -> int:
    app = create_app(
        state_path=args.state,
        admin_token=os.environ.get("BACKPLANE_ADMIN_TOKEN"),
    )
    uvicorn.run(
        app,
        host=args.host,
        port=args.port,
        access_log=args.access_log,
        server_header=False,
        limit_concurrency=args.limit_concurrency,
        timeout_keep_alive=5,
    )
    return 0


def command_keygen(args: argparse.Namespace) -> int:
    keygen(args.private, args.public)
    print(f"wrote {args.private} and {args.public}")
    return 0


def command_pack(args: argparse.Namespace) -> int:
    manifest = CardManifest.model_validate_json(Path(args.manifest).read_text())
    config = json.loads(Path(args.config).read_text())
    package = build_package(manifest, config, args.output, args.private)
    _json(package.model_dump(mode="json"))
    return 0


def command_verify(args: argparse.Namespace) -> int:
    trusted = frozenset(args.trusted or [])
    package = load_package(
        args.package,
        PackageTrustPolicy(require_signature=True, trusted_fingerprints=trusted),
    )
    _json(package.model_dump(mode="json"))
    return 0


def command_smoke(_: argparse.Namespace) -> int:
    with tempfile.TemporaryDirectory(prefix="backplane-smoke-") as folder:
        runtime = build_demo_runtime(Path(folder) / "state.sqlite3")

        async def run() -> None:
            prompts = {
                "support": "What is the refund window?",
                "operations": "Report line 7 temperature.",
                "research": "Calculate 17 * 23",
            }
            results = await asyncio.gather(
                *[
                    runtime.run(
                        GenerationRequest(tenant_id=tenant, prompt=prompt),
                        authenticate=False,
                    )
                    for tenant, prompt in prompts.items()
                ]
            )
            _json(
                {
                    "status": "pass",
                    "tenants": {
                        result.receipt.tenant_id: {
                            "cards": result.receipt.card_stack,
                            "status": result.receipt.status,
                            "receipt": result.receipt.receipt_id,
                        }
                        for result in results
                    },
                }
            )
            await runtime.close()

        asyncio.run(run())
    return 0


def command_benchmark(args: argparse.Namespace) -> int:
    _json(
        run_benchmark_sync(
            requests_per_tenant=args.requests_per_tenant,
            concurrency=args.concurrency,
        )
    )
    return 0


def parser() -> argparse.ArgumentParser:
    root = argparse.ArgumentParser(
        prog="backplane",
        description="Governed extension runtime for open-weight models.",
    )
    sub = root.add_subparsers(dest="command", required=True)

    demo = sub.add_parser("demo", help="run the multi-tenant control-plane sandbox")
    demo.add_argument("--tenant", choices=("support", "operations", "research"), default="support")
    demo.add_argument("--prompt", default="What is the refund window?")
    demo.add_argument("--state", default="./work/backplane-demo.sqlite3")
    demo.set_defaults(func=command_demo)

    serve = sub.add_parser("serve", help="serve the API")
    serve.add_argument("--host", default="127.0.0.1")
    serve.add_argument("--port", type=int, default=8994)
    serve.add_argument(
        "--state",
        default=None,
        help="SQLite state path (defaults to BACKPLANE_STATE, then ./work/backplane-state.sqlite3)",
    )
    serve.add_argument("--limit-concurrency", type=int, default=64)
    serve.add_argument("--access-log", action="store_true")
    serve.set_defaults(func=command_serve)

    keys = sub.add_parser("keygen", help="create an Ed25519 card signing keypair")
    keys.add_argument("--private", required=True)
    keys.add_argument("--public", required=True)
    keys.set_defaults(func=command_keygen)

    pack = sub.add_parser("pack", help="build a signed declarative card package")
    pack.add_argument("--manifest", required=True)
    pack.add_argument("--config", required=True)
    pack.add_argument("--private", required=True)
    pack.add_argument("--output", required=True)
    pack.set_defaults(func=command_pack)

    verify = sub.add_parser("verify", help="verify a signed card package")
    verify.add_argument("package")
    verify.add_argument("--trusted", action="append")
    verify.set_defaults(func=command_verify)

    smoke = sub.add_parser("smoke", help="run the three-tenant acceptance smoke")
    smoke.set_defaults(func=command_smoke)

    benchmark = sub.add_parser(
        "benchmark", help="benchmark control-plane isolation and scheduling"
    )
    benchmark.add_argument("--requests-per-tenant", type=int, default=25)
    benchmark.add_argument("--concurrency", type=int, default=12)
    benchmark.set_defaults(func=command_benchmark)
    return root


def main(argv: list[str] | None = None) -> int:
    args = parser().parse_args(argv)
    return int(args.func(args))


if __name__ == "__main__":
    raise SystemExit(main())
