# -*- coding: utf-8 -*-
"""anu_v3.batch_runtime_reconciler — top-level +29 reconciler.

Consumes (read-only):
  * memory/fixtures/task-2553.runtime-reconcile.fixture.json   (normalized)
  * memory/events/task-2553.parallel-batch-state.json          (frozen v1)

Emits (NEW writable authority — separate path, 9-R.2):
  * memory/events/task-2553.parallel-runtime-registry.batch-state.json

Standalone (9-R.3): zero coupling to anu_v3.parallel_batch_coordinator. The
frozen durable v1 file is read for provenance only and is NEVER mutated.

batch_state is the single authority (구현목표 9). It represents +26 MERGED,
+27 PASS, +28 DONE simultaneously (regression 13). consolidated_summary
carries final decision fields only (regression 15). closeout eligibility is
derived from batch_state and only PROPOSED (regression 14, §7).
"""
from __future__ import annotations

import hashlib
import json
from pathlib import Path
from typing import Dict, Optional

from anu_v3.parallel_runtime_registry import ParallelRuntimeRegistry
from anu_v3.batch_runtime_join_policy import (
    TrackJoinView,
    join,
    derive_closeout_proposal,
)

SCHEMA = "anu_v3.parallel_runtime_registry.v1"
BATCH_ID = "batch-task-2553-runtime-registry-2553p29"


def _sha256(path: Path) -> str:
    return hashlib.sha256(Path(path).read_bytes()).hexdigest()


def reconcile(
    fixture_path: Path,
    frozen_state_path: Path,
    *,
    contamination: Optional[list] = None,
    generated_ts_kst: str = "",
) -> Dict[str, object]:
    """Build the authority batch-state document (does not write it)."""
    reg = ParallelRuntimeRegistry.load_fixture(fixture_path)
    reg.reconcile_all()
    records = reg.records_json()

    # frozen durable v1 — read-only, provenance only (zero mutation/coupling).
    frozen_ref = str(frozen_state_path)
    frozen_sha = _sha256(frozen_state_path) if Path(frozen_state_path).is_file() else ""

    contamination = list(contamination or [])

    views = [
        TrackJoinView(
            task_id=rec["task_id"],
            terminal_outcome=rec["terminal_outcome"],
            classification=rec["classification"],
            hold_for_chair=rec["hold_for_chair"],
            fallback_state=rec["fallback_state"],
            has_result=rec["result_present"] or rec["done_present"],
        )
        for rec in records.values()
    ]
    jr = join(views, contamination=contamination)

    batch_state: Dict[str, object] = {
        "schema": SCHEMA,
        "batch_id": BATCH_ID,
        "source_fixture": str(fixture_path),
        "source_fixture_sha256": _sha256(fixture_path),
        "frozen_batch_state_ref": frozen_ref,
        "frozen_batch_state_sha256": frozen_sha,
        "frozen_coupling": "read-only provenance only; zero mutation/coupling (9-R.2/9-R.3)",
        "generated_ts_kst": generated_ts_kst,
        "tracks": records,
        "join_policy": {
            "independent_done_tracks": jr.independent_done_tracks,
            "held_tracks": jr.held_tracks,
            "waiting_tracks": jr.waiting_tracks,
            "blocking_relations": jr.blocking_relations,
        },
        "missing_normal_collector": reg.missing_normal_collectors(),
        "contamination": contamination,
        "batch_next_action": jr.batch_next_action,
    }

    # 구현목표 12 / regression 14 — closeout derived from batch_state only.
    proposal = derive_closeout_proposal(batch_state)
    batch_state["closeout_proposal"] = proposal

    # regression 15 — consolidated summary == final decision fields ONLY.
    batch_state["consolidated_summary"] = {
        "tracks": {
            tid: {
                "terminal_outcome": rec["terminal_outcome"],
                "classification": rec["classification"],
                "hold_for_chair": rec["hold_for_chair"],
            }
            for tid, rec in records.items()
        },
        "batch_next_action": jr.batch_next_action,
        "closeout_eligible": bool(proposal["eligible"]),
    }
    return batch_state


def write_authority_state(batch_state: Dict[str, object], out_path: Path) -> Path:
    """Write the NEW authority file (9-R.2 — separate path, never the frozen v1).

    Hard guard: refuses to write the frozen durable v1 path.
    """
    out = Path(out_path)
    if out.name == "task-2553.parallel-batch-state.json":
        raise RuntimeError(
            "refusing to write frozen durable v1 (9-R.2): authority file must "
            "be the separate task-2553.parallel-runtime-registry.batch-state.json"
        )
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(
        json.dumps(batch_state, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )
    return out
