"""utils/normal_completion_callback_collector_entrypoint.py
task-2728 — normal completion callback collector entrypoint (registry-based idempotent prune wiring).
normal callback/result 수집/envelope CONFIRMED/terminal marker 중 하나라도 성립 시 동일 (task_id·round·head) pending fallback을 즉시 prune.
"""
from __future__ import annotations

import logging
from pathlib import Path
from typing import Callable, Optional

from utils.completion_callback_fallback_cancel import (
    prune_fallbacks_for_key,
    detect_unwired_fallback,
    _now_utc,
)
from utils.callback_envelope_schema import CANONICAL_ROOT_DEFAULT

COLLECTOR_ENTRYPOINT_SCHEMA = "normal_completion_callback_collector_entrypoint.v1"
RESULT_JSON_MISSING_RECOVERED = "RESULT_JSON_MISSING_RECOVERED_BY_COLLECTOR"

logger = logging.getLogger(__name__)


def resolve_canonical_root(
    canonical_root: Optional[str] = None,
    worktree_path: Optional[str] = None,
) -> tuple:
    """canonical_root/worktree_path 주입 우선, autoset 추측 금지.

    Returns (root: str, source: str).
    """
    if worktree_path is not None:
        return (worktree_path, "worktree_path")
    elif canonical_root is not None:
        return (canonical_root, "canonical_root_injected")
    else:
        return (CANONICAL_ROOT_DEFAULT, "default")


def resolve_result_json_path(
    task_id: str,
    *,
    canonical_root: Optional[str] = None,
    worktree_path: Optional[str] = None,
    result_json_path: Optional[str] = None,
) -> Path:
    """result_json_path 우선, 아니면 resolve_canonical_root로 root 구한 뒤
    Path(root)/"memory"/"results"/f"{task_id}.result.json".
    autoset 추측 금지.
    """
    if result_json_path is not None:
        return Path(result_json_path)
    root, _ = resolve_canonical_root(canonical_root, worktree_path)
    return Path(root) / "memory" / "results" / f"{task_id}.result.json"


def collect_and_prune(
    *,
    task_id: str,
    round: int,
    head_sha: str,
    trigger: str,
    canonical_root: Optional[str] = None,
    worktree_path: Optional[str] = None,
    result_json_path: Optional[str] = None,
    registry_path: Optional[str] = None,
    remover=None,
    dry_run: bool = True,
    normal_callback_collected: bool = True,
    dispatch_fired: bool = True,
    now_fn: Callable[[], str] = _now_utc,
) -> dict:
    """normal callback/result 수집 후 동일 (task_id·round·head) pending fallback을 즉시 prune.

    trigger ∈ {"normal_callback_collected","result_collected","envelope_confirmed","terminal_marker"}
    (검증은 느슨하게 — 그냥 기록).
    """
    root, root_source = resolve_canonical_root(canonical_root, worktree_path)

    # result.json contract
    rjp = resolve_result_json_path(
        task_id,
        canonical_root=canonical_root,
        worktree_path=worktree_path,
        result_json_path=result_json_path,
    )
    result_json_exists = rjp.exists()
    follow_up_required = False
    if result_json_exists:
        result_json_status = "OK"
    else:
        result_json_status = RESULT_JSON_MISSING_RECOVERED
        follow_up_required = True  # collector가 보정 진행 — prune은 계속
        logger.warning(
            "RESULT_JSON_MISSING: task_id=%s round=%s head_sha=%s path=%s — %s",
            task_id, round, head_sha, rjp, RESULT_JSON_MISSING_RECOVERED,
        )

    # registry 기반 idempotent prune
    outcomes = prune_fallbacks_for_key(
        task_id=task_id,
        round=round,
        head_sha=head_sha,
        trigger=trigger,
        remover=remover,
        dry_run=dry_run,
        canonical_root=canonical_root,
        registry_path=registry_path,
        now_fn=now_fn,
    )

    # unwired fallback 감지 (outcomes가 비어있을 때만)
    unwired = None
    if not outcomes:
        unwired = detect_unwired_fallback(
            task_id=task_id,
            round=round,
            head_sha=head_sha,
            dispatch_fired=dispatch_fired,
            canonical_root=canonical_root,
            registry_path=registry_path,
            now_fn=now_fn,
        )

    return {
        "schema": COLLECTOR_ENTRYPOINT_SCHEMA,
        "task_id": task_id,
        "round": round,
        "head_sha": head_sha,
        "trigger": trigger,
        "normal_callback_collected": bool(normal_callback_collected),
        "canonical_root": root,
        "canonical_root_source": root_source,
        "result_json_exists": bool(result_json_exists),
        "result_json_status": result_json_status,
        "follow_up_required": follow_up_required,
        "prune_outcomes": [o.to_dict() for o in outcomes],
        "unwired": (unwired.to_dict() if unwired else None),
        "ts_utc": now_fn(),
    }
