#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""G4.6 canary — visible_entities_validator contract smoke.

Synthesized fixture (시나리오 의존 token 0) 로 validator 의 4 source path
모두 fail-fast 동작 확인. fixture-only — LLM 호출 없음.

Codex iter 1 B1 carry — validator signature 변경 (db / project_id 인자 제거).
caller 가 main thread 에서 prebuild 한 name_by_short_id map 만 전달.
canary 도 dict 만 만듦.

Scope: visible_entities_validator.validate_visible_entities_contract().

Generic logic — 시나리오 의존 어휘 0. ID coverage primary + dynamic
entity_canon.name secondary 동작만 검증.

Usage:
    python scripts/canary/g4_6_visible_entities_contract.py \\
        --output results/g4_6_visible_entities_contract.json
"""
from __future__ import annotations

import argparse
import json
import sys
from datetime import datetime, timezone
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT / "backend"))

from app.core.errors import AppError  # noqa: E402
from app.core.visible_entities_validator import (  # noqa: E402
    validate_visible_entities_contract,
)


def iso8601_now() -> str:
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


def _rpc_empty() -> dict:
    return {
        "asset_requirements": {"required_refs": []},
        "id_policy": {"allowed_outlook_pairs": []},
    }


def _run_smoke() -> dict:
    """4 fixture path 검증 — pass 1 + fail 3."""
    results = []

    # Path 1 — pass: visible 모두 prompt 등장
    name_map_single = {"C08": "Adult Character A"}
    shot = {
        "scene_index": 1, "_shot_index": 1,
        "visible_entities": ["C08"],
        "t2i_variations": [{"t2i_prompt": "C08 standing at the door."}],
        "render_prompt_card": _rpc_empty(),
    }
    try:
        validate_visible_entities_contract(shot, name_map_single)
        results.append({"path": "source_1_pass", "status": "ok"})
    except AppError as exc:
        results.append({
            "path": "source_1_pass", "status": "unexpected_fail",
            "code": exc.code, "message": exc.message,
        })

    # Path 2 — Source 1 forward fail (visible C09 missing in prompt)
    name_map_pair = {"C08": "Adult Character A", "C09": "Adult Character B"}
    shot = {
        "scene_index": 1, "_shot_index": 1,
        "visible_entities": ["C08", "C09"],
        "t2i_variations": [{"t2i_prompt": "C08 standing alone."}],
        "render_prompt_card": _rpc_empty(),
    }
    try:
        validate_visible_entities_contract(shot, name_map_pair)
        results.append({"path": "source_1_forward", "status": "unexpected_pass"})
    except AppError as exc:
        results.append({
            "path": "source_1_forward",
            "status": (
                "ok" if "visible_id_not_in_prompt" in exc.code
                else "wrong_code"
            ),
            "code": exc.code,
        })

    # Path 3 — Source 2 fail (entity_name no specific ID anchor)
    long_filler = " filler " * 12
    shot = {
        "scene_index": 1, "_shot_index": 1,
        "visible_entities": ["C08", "C09"],
        "t2i_variations": [{
            "t2i_prompt": (
                "C08O10 in dark jacket, Adult Character B in white blouse." +
                long_filler +
                "Beside, C09 standing afar."
            ),
        }],
        "render_prompt_card": _rpc_empty(),
    }
    try:
        validate_visible_entities_contract(shot, name_map_pair)
        results.append({"path": "source_2_specific_id", "status": "unexpected_pass"})
    except AppError as exc:
        results.append({
            "path": "source_2_specific_id",
            "status": (
                "ok" if "entity_name_no_specific_id" in exc.code
                else "wrong_code"
            ),
            "code": exc.code,
        })

    # Path 4 — Source 3a fail (required_refs char base not visible)
    shot = {
        "scene_index": 1, "_shot_index": 1,
        "visible_entities": ["C08"],
        "t2i_variations": [{"t2i_prompt": "C08 standing alone."}],
        "render_prompt_card": {
            "asset_requirements": {"required_refs": [
                {"kind": "character_outlook", "id": "C09O11"},
            ]},
            "id_policy": {"allowed_outlook_pairs": []},
        },
    }
    try:
        validate_visible_entities_contract(shot, name_map_single)
        results.append({"path": "source_3a_required_refs", "status": "unexpected_pass"})
    except AppError as exc:
        results.append({
            "path": "source_3a_required_refs",
            "status": (
                "ok" if "required_ref_not_visible" in exc.code
                else "wrong_code"
            ),
            "code": exc.code,
        })

    return {
        "captured_at": iso8601_now(),
        "module": "visible_entities_validator",
        "fixture_kind": "synthesized_generic",
        "paths": results,
        "summary": {
            "total_paths": len(results),
            "passed": sum(1 for r in results if r["status"] == "ok"),
        },
    }


def main() -> int:
    parser = argparse.ArgumentParser(
        description="G4.6 canary: visible_entities_validator contract smoke."
    )
    parser.add_argument("--output", type=str, required=True)
    args = parser.parse_args()

    out = _run_smoke()

    output_path = Path(args.output)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(out, indent=2, ensure_ascii=False), encoding="utf-8")

    summary = out["summary"]
    print(json.dumps(summary, indent=2))

    return 0 if summary["passed"] == summary["total_paths"] else 1


if __name__ == "__main__":
    raise SystemExit(main())
