"""CLI adapter for the worker audit-sidecar contract (`okstra worker-audit-check`).

Phase 7 runs the same rules through `validate-run.py`, but by then the worker
session is gone and the only remedies left are a retroactive edit — which breaks
the audit chain — or a failed run. Called the moment a worker returns, the same
rules cost one message to a worker that is still listening.
"""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

from okstra_ctl.worker_audit_ledger import (
    worker_result_files,
    worker_results_audit_findings,
)

# Phase 7 은 같은 규칙을 두 등급으로 판정한다. 여기서 그 등급을 함께 내지 않으면
# exit 2 하나로 "run 이 실패한다" 고 읽힌다 — 2026-09-05 실측(dev-10626 stage-1)에서
# 리드가 권고 등급의 미매칭 인용을 차단 실패로 읽고 결과를 거절·재배치했다.
RUN_IMPACT = (
    "blocking rows fail the run at Phase 7 (validate-run); advisory rows do not "
    "fail the run at Phase 7 and can only be fixed now, while the worker is alive"
)


_CLI_EPILOG = r"""Usage:
  okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <type> \
    --seq <nnn> [--worker <id>]

  --run-dir    the run directory; worker-results/ and prompts/ hang off it
  --seq        this run's seq; a bare number is zero-padded to three digits
  --worker     check only this worker (default: every worker in the run)

Emits one JSON object — `{ok, inspected, inspectedFiles[], failures[],
blocking[], advisory[], runImpact}` — and exits 2 when failures[] is
non-empty, 0 otherwise. `failures` is `blocking` followed by `advisory`.

A selector that matches no result file also exits 2, carrying `selectorError`
instead of failures: nothing was checked, which is not a pass.

Runs the Phase 7 audit-sidecar rules now, while the worker session is still
alive: that every result file carries no `## 0. Reading Confirmation` heading,
that its audit sidecar exists (blocking), and that every backticked `path:line`
citation has a matching Evidence read row in that sidecar (advisory).

Exit 2 means "fix it now", not "the run fails": only `blocking` rows fail the
run at Phase 7, and `advisory` rows are the ones nothing can repair once the
worker session is gone. Do not reject or re-dispatch a result over an advisory
row alone.
"""


# 결과 파일명은 seq 를 3자리로 적고, 선택은 그 문자열과의 동등 비교다
# (`worker_audit_ledger.worker_result_files`). `--seq 1` 은 `001` 과 같지 않아
# 전건이 걸러지고, 검사 0건은 실패 0건이므로 통과로 읽혔다 — 2026-09-10
# dev-10642-15 final-verification 001 실측.
def _normalized_seq(value: str) -> str:
    text = value.strip()
    return text.zfill(3) if text.isdigit() else text


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        epilog=_CLI_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        prog="okstra worker-audit-check",
        description="Check one run's worker audit sidecars (read-only).",
    )
    parser.add_argument("--run-dir", type=Path, required=True,
                        help="runs/<task-type>/ for this run")
    parser.add_argument("--task-type", required=True)
    parser.add_argument("--seq", required=True, type=_normalized_seq,
                        help="this run's seq; a bare number is zero-padded to "
                             "three digits")
    parser.add_argument("--worker", default=None,
                        help="check only this worker id, with or without the "
                             "`-worker` suffix (default: every worker)")
    return parser


def main(argv: list[str] | None = None) -> int:
    args = _parser().parse_args(argv)
    blocking, advisory = worker_results_audit_findings(
        args.run_dir, args.task_type, args.seq, worker=args.worker
    )
    failures = blocking + advisory
    # A selector that narrows to nothing also produces no failures, so `ok`
    # alone cannot tell a real pass from a check that judged zero files —
    # a mistyped `--worker` used to read as a clean bill of health. The count
    # is what makes the two distinguishable at a glance.
    inspected = [
        path.name
        for path, _role, _seq in worker_result_files(
            args.run_dir, args.task_type, args.seq, args.worker
        )
    ]
    # 검사한 파일이 없다는 것과 위반이 없다는 것은 다른 사실이고, `ok` 는 둘을
    # 구분하지 못한다. 선택자가 아무것도 고르지 못했으면 판정 자체가 없었으므로
    # 통과로 보고하지 않는다.
    selector_error = "" if inspected else (
        f"no worker-results file matched task-type={args.task_type} "
        f"seq={args.seq}"
        + (f" worker={args.worker}" if args.worker else "")
        + f" under {args.run_dir}"
    )
    payload = {
        "ok": bool(not failures and inspected),
        "inspected": len(inspected),
        "inspectedFiles": inspected,
        "failures": failures,
        "blocking": blocking,
        "advisory": advisory,
        "runImpact": RUN_IMPACT,
    }
    if selector_error:
        payload["selectorError"] = selector_error
    print(json.dumps(payload, ensure_ascii=False, indent=2))
    return 2 if failures or selector_error else 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))
