"""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


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        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:]))
