"""검증된 호출에서 배치 입력을 생성하고 전체 검사 뒤 불변 발행한다."""
from __future__ import annotations

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

from ..invocation import (
    _exclusive_lock, _publish_or_reuse_exact, verify_agent_invocation,
)
from ...dispatch_state import worker_jobs_from_payload, validate_dispatch_prompts, prompt_anchor_values
from ...models import provider_wrappers
from ...path_hints import hydrate_active_run_context
from ...paths import okstra_home
from .inputs import (
    AgentPromptCliError, _authorized_path, _project_input, _project_root,
    _read_json_object,
)


def generate_jobs(args: argparse.Namespace) -> dict[str, object]:
    project = _project_root(args.project_root)
    manifest_path = _project_input(project, args.run_manifest, "run manifest")
    manifest = _read_json_object(manifest_path, "run manifest")
    if manifest.get("executionIdentityVersion") != 2:
        raise AgentPromptCliError("agent-prompt jobs requires a v2 run manifest")
    output = _authorized_path(project, args.out, [manifest["runDirectoryPath"]], "jobs", must_exist=False)
    paths = [_project_input(project, value, "metadata") for value in args.metadata]
    rows = [_metadata_job(project, manifest_path, manifest, path, args.dispatch_kind) for path in paths]
    refs = [(row["invocationRef"], row["attempt"]) for row in rows]
    if len(set(refs)) != len(refs):
        raise AgentPromptCliError("jobs metadata contains duplicate invocation attempts")
    context_path = _project_input(project, manifest["activeRunContextPath"], "active run context")
    context = hydrate_active_run_context(_read_json_object(context_path, "active run context"))
    payload = {"dispatchKind": args.dispatch_kind, "workers": rows}
    wrappers = provider_wrappers("analyser")
    jobs = worker_jobs_from_payload(
        project, payload, manifest=manifest, active_context=context,
        backend="cli-wrapper", idle_timeout_seconds=None,
        default_dispatch_kind=args.dispatch_kind,
        resolve_wrapper=lambda provider: okstra_home() / "bin" / wrappers[provider],
        default_provider=lambda worker_id: worker_id,
    )
    validate_dispatch_prompts(manifest, context, jobs)
    body = (json.dumps(payload, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
    with _exclusive_lock(output.with_name(f".{output.name}.lock")):
        if output.exists() and output.read_bytes() != body:
            raise AgentPromptCliError(f"jobs output differs; existing file preserved: {output}; choose a new --out path")
        _publish_or_reuse_exact(output, body)
    return {"ok": True, "jobsPath": str(output), "workers": len(rows)}


def _metadata_job(
    project: Path, manifest_path: Path, manifest: Mapping[str, Any],
    metadata_path: Path, dispatch_kind: str,
) -> dict[str, object]:
    errors = verify_agent_invocation(
        metadata_path, project_root=project, expected_run_manifest_path=manifest_path,
    )
    if errors:
        raise AgentPromptCliError(f"{metadata_path}: " + "; ".join(errors))
    metadata = _read_json_object(metadata_path, "agent metadata")
    if metadata.get("executionIdentityVersion") != 2 or metadata["dispatchKind"] != dispatch_kind:
        raise AgentPromptCliError(f"{metadata_path}: expected v2 dispatchKind {dispatch_kind!r}")
    execution = next(row for row in manifest["roleExecutions"] if row["roleExecutionRef"] == metadata["roleExecutionRef"])
    if execution["role"] == "translator":
        raise AgentPromptCliError("translator uses canonical reservation dispatch: worker-dispatch --workers translator")
    prompt = _project_input(project, metadata["prompt"]["path"], "prompt")
    anchors = prompt_anchor_values(prompt, ("Result Path", "Worker Result Path"))
    result = anchors.get("Worker Result Path") or anchors.get("Result Path")
    if not result:
        raise AgentPromptCliError(f"prompt has no result anchor: {prompt}")
    assignment = metadata["modelAssignment"]
    if assignment["provider"] not in provider_wrappers("analyser"):
        raise AgentPromptCliError(f"unsupported worker provider: {assignment['provider']}")
    return {
        **{key: metadata[key] for key in (
            "schemaVersion", "executionIdentityVersion", "participantRef", "roleExecutionRef",
            "executionLabel", "dutyId", "invocationRef", "attempt", "invocationId",
            "audience", "assignmentRef", "digests",
        )},
        "provider": assignment["provider"], "modelExecutionValue": assignment["modelExecutionValue"],
        "hostModelValue": assignment["hostModelValue"], "role": execution["role"],
        "promptPath": str(prompt), "promptMetadataPath": str(metadata_path),
        "workerResultPath": result, "enforcementMode": "core-pre-dispatch",
    }
