"""동적 검증자 요청을 예약하고, 그 출처가 자격이 있는지 본다.

재검증 라운드에서 리드가 추가 검증자를 요청할 수 있고, 그 요청은 예약(슬롯
확보)과 자격 검사(요청한 role 이 이 run 에서 그럴 수 있는가)를 함께 통과해야
한다. 두 가지가 한 파일에 있는 이유는 예약이 자격 검사를 통과한 뒤에만
일어나야 하기 때문이다 — 순서가 곧 계약이다.
"""
from __future__ import annotations

import argparse
from dataclasses import replace
import hashlib
from pathlib import Path
from typing import Any, Mapping

from ..invocation import (
    AgentInvocationRequest,
    AgentModelAssignment,
    compose_agent_prompt,
    compose_unbound_run_prompt,
    agent_prompt_task_bytes,
    v2_role_assignment_authority_errors,
)
from ...convergence_store import (
    DYNAMIC_VERIFIER_SOURCE_ROLES,
    reserve_dynamic_verifier,
)
from ...worker_prompt_policy import (
    CRITIC_VERIFY_DISPATCH_KIND,
    PLAN_VERIFY_DISPATCH_KIND_PREFIX,
    is_plan_critic_verification,
    verification_dispatch_round,
)
from .inputs import AgentPromptCliError


def _reserve_dynamic_verifier_request(
    request: AgentInvocationRequest,
    *,
    args: argparse.Namespace,
    manifest: Mapping[str, Any],
    manifest_path: Path,
    source_role_execution_ref: str,
) -> AgentInvocationRequest:
    _validate_dynamic_source_assignment(
        manifest,
        source_role_execution_ref,
        request.assignment,
    )
    prompt_bytes = compose_unbound_run_prompt(request)
    verifier, invocation = reserve_dynamic_verifier(
        manifest_path,
        source_role_execution_ref=source_role_execution_ref,
        duty_id=args.audience,
        round_number=_reservation_round(args.dispatch_kind),
        dispatch_kind=args.dispatch_kind,
        task_key=_required_manifest_string(manifest, "taskKey"),
        input_digest="sha256:" + hashlib.sha256(agent_prompt_task_bytes(prompt_bytes)).hexdigest(),
        invocation_ref=args.invocation_id,
    )
    bound = replace(
        request,
        worker_id=None,
        participant_ref=verifier.participant_ref,
        role_execution_ref=verifier.role_execution_ref,
        duty_id=args.audience,
        invocation_ref=invocation.invocation_ref,
    )
    if agent_prompt_task_bytes(compose_agent_prompt(bound).encode("utf-8")) != agent_prompt_task_bytes(prompt_bytes):
        raise AgentPromptCliError(
            "dynamic verifier identity changed immutable prompt bytes"
        )
    return bound


def _dynamic_verifier_source(
    args: argparse.Namespace,
    manifest: Mapping[str, Any],
) -> str | None:
    is_v2 = (
        manifest.get("schemaVersion") == "2.0"
        and manifest.get("executionIdentityVersion") == 2
    )
    is_reverify = (
        (args.assignment_ref.startswith("reverify/") or is_plan_critic_verification(
            task_type=str(manifest.get("taskType", "")),
            assignment_ref=args.assignment_ref, dispatch_kind=args.dispatch_kind,
        ))
        and args.audience == "reverification-worker"
    )
    source = args.source_role_execution_ref
    if is_v2 and is_reverify:
        if args.replace_undispatched:
            raise AgentPromptCliError(
                "v2 dynamic verifier prompts are append-only: this "
                "invocation's reservation already recorded this prompt's input "
                "digest in the run manifest, and a reservation cannot be "
                "rewritten. Use a fresh invocation ID and prompt path."
            )
        if not source:
            raise AgentPromptCliError(
                "v2 reverify materialization requires "
                "--source-role-execution-ref"
            )
        return source
    if source:
        raise AgentPromptCliError(
            "--source-role-execution-ref is allowed only for v2 reverify"
        )
    return None


def _reservation_round(dispatch_kind: str) -> int:
    """예약에 적는 라운드 번호. critic gap 검증은 라운드 원장 밖이라 1 이다 —
    dispatch 가 attempt 행에 적는 값(`dispatch_state._execution_dispatch_round`)과
    같아야 한다. 두 계산이 갈리면 validate-run 이 그 디스패치를 거부한다."""
    round_number = verification_dispatch_round(dispatch_kind)
    if round_number is None:
        raise AgentPromptCliError(
            "dynamic verifier dispatch kind must be reverify-r<N>, "
            f"{PLAN_VERIFY_DISPATCH_KIND_PREFIX}<N>, or "
            f"{CRITIC_VERIFY_DISPATCH_KIND}"
        )
    return round_number


def _validate_dynamic_source_assignment(
    manifest: Mapping[str, Any],
    source_role_execution_ref: str,
    assignment: AgentModelAssignment,
) -> None:
    rows = manifest.get("roleExecutions")
    source = next(
        (
            row
            for row in rows
            if isinstance(row, Mapping)
            and row.get("roleExecutionRef") == source_role_execution_ref
        ),
        None,
    ) if isinstance(rows, list) else None
    if source is None:
        raise AgentPromptCliError(
            f"source role execution is unknown: {source_role_execution_ref}"
        )
    if source.get("role") not in DYNAMIC_VERIFIER_SOURCE_ROLES:
        raise AgentPromptCliError(
            "dynamic verifier source role is not eligible for re-verification"
        )
    errors = v2_role_assignment_authority_errors(
        manifest,
        role_execution_ref=source_role_execution_ref,
        participant_ref=str(source.get("participantRef") or ""),
        assignment=assignment,
    )
    if errors:
        raise AgentPromptCliError(
            "dynamic verifier source does not match the selected assignment: "
            + "; ".join(errors)
        )


def _required_manifest_string(
    manifest: Mapping[str, Any],
    key: str,
) -> str:
    value = manifest.get(key)
    if not isinstance(value, str) or not value.strip():
        raise AgentPromptCliError(f"run manifest has no {key}")
    return value
