"""Provider-neutral dispatcher for verified CLI-backed worker assignments."""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Any, Mapping, Sequence

from . import dispatch_core
from .adapters.dispatch.cli_wrapper import CliWrapperDispatchPort
from .application.dispatch_assignments import dispatch_assignments
from .dispatch_state import DispatchError
from .models import provider_wrappers
from .ports.worker_dispatch import WorkerDispatchRequest


SUPPORTED_CLI_WORKERS = provider_wrappers("analyser")


def main(argv: Sequence[str] | None = None) -> int:
    args = _parser().parse_args(argv)
    try:
        if args.jobs_file and args.workers:
            raise DispatchError("--jobs-file and --workers cannot be combined")
        request = _dispatch_request(args)
        port = CliWrapperDispatchPort(
            supported_worker_wrappers=SUPPORTED_CLI_WORKERS,
            unsupported_worker_label="worker-dispatch",
            required_lead_runtime=None,
            cli_wrapper_assignments_only=True,
            default_provider_by_worker_id={"report-writer": "claude"},
        )
        plan = dispatch_assignments(request, port)
        backend_plan = plan.backend_plan
        if backend_plan is None:
            raise DispatchError("CLI wrapper dispatch did not produce a backend plan")
        if args.dry_run:
            _print_json(_backend_payload(backend_plan, dry_run=True))
            return 0
        code = dispatch_core.dispatch_cli_wrapper_plan(backend_plan)
        _print_json({**_backend_payload(backend_plan, dry_run=False), "exitCode": code})
        return code
    except DispatchError as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2


def _dispatch_request(args: argparse.Namespace) -> WorkerDispatchRequest:
    return WorkerDispatchRequest(
        project_root=Path(args.project_root),
        run_manifest=Path(args.run_manifest),
        workspace_root=Path(args.workspace_root),
        okstra_bin=Path(args.okstra_bin) if args.okstra_bin else None,
        requested_workers=tuple(_parse_workers(args.workers)),
        idle_timeout_seconds=_parse_idle_timeout(args.idle_timeout_seconds),
        dispatch_kind=args.dispatch_kind,
        jobs_file=Path(args.jobs_file) if args.jobs_file else None,
    )


def _backend_payload(backend_plan, *, dry_run: bool) -> dict[str, Any]:
    payload = backend_plan.to_payload(dry_run=dry_run)
    payload.pop("workerDispatches", None)
    return payload


_CLI_EPILOG = r"""Usage:
  okstra worker-dispatch --project-root <dir> --run-manifest <path> \
    [--workers <cli-assigned-worker-ids>] [--dry-run] \
    [--idle-timeout-seconds <n>]

This command reads a prepared run manifest and dispatches only assignments
whose persisted runner is 'cli-wrapper'. It verifies each invocation contract
before execution and uses the persisted provider, model, and registered CLI.
Native-session assignments remain owned by the active host session.

Missing worker prompt files are generated automatically from the immutable run snapshot.
Existing prompt and metadata files are never overwritten.
When report-writer completes, this command also runs the idempotent post-report
steps: token-usage substitution, render-views, spawn-followups, and validate-run.

  --workspace-root and --okstra-bin are owned by this command.
"""


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        epilog=_CLI_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        prog="okstra worker-dispatch",
        description="Dispatch verified CLI-backed workers for a prepared run.",
    )
    parser.add_argument("--project-root", required=True)
    parser.add_argument("--run-manifest", required=True)
    parser.add_argument("--workspace-root", required=True)
    parser.add_argument("--okstra-bin", default="")
    parser.add_argument("--workers", default="")
    parser.add_argument("--dry-run", action="store_true")
    parser.add_argument("--idle-timeout-seconds", default="")
    parser.add_argument("--dispatch-kind", default="initial")
    parser.add_argument("--jobs-file", default="")
    return parser


def _parse_workers(raw: str) -> list[str]:
    return [item.strip() for item in raw.split(",") if item.strip()]


def _parse_idle_timeout(raw: str) -> int | None:
    """명시된 예산, 또는 역할이 정하게 두는 ``None``."""
    if not raw:
        return None
    try:
        value = int(raw)
    except ValueError as exc:
        raise DispatchError("--idle-timeout-seconds must be an integer") from exc
    if value < 0:
        raise DispatchError("--idle-timeout-seconds must be non-negative")
    return value


def _print_json(payload: Mapping[str, Any]) -> None:
    print(json.dumps(payload, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
