#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""G4.3 canary — token-count delta (system.md, tiktoken cl100k_base) (Task 4.5).

Computes baseline (v17) and candidate (v18) token counts for
`prompts/_base/scene_detail/<version>/system.md` and emits the signed delta.

Override R4-B4 carry: signed convention everywhere —
  token_delta = candidate_token_count - baseline_token_count
  pass condition: token_delta <= -1050
which is mathematically equivalent to baseline - candidate >= 1050.

Override R1R2-B2 / R2-I3 carry: token gate `≥ -1050` after Phase 2 ID-policy
lift. Estimate -1065 with 15-token slack.

Exit criteria (analyzer):
  exit_criteria_pass == True  iff  token_delta <= -1050  (default threshold)

Usage (config mode):
    python scripts/canary/g4_3_token_count.py \\
        --config canary_config.json \\
        --baseline-prompt-version 17.202605042018 \\
        --candidate-prompt-version 18.<timestamp> \\
        --output results/g4_3_token_count.json
"""
from __future__ import annotations

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

sys.path.insert(0, str(Path(__file__).resolve().parent))
from _g4_3_common import (  # noqa: E402
    load_pinning_from_args,
    validate_pinning,
)


def iso8601_now() -> str:
    return datetime.utcnow().isoformat() + "Z"


def main() -> int:
    parser = argparse.ArgumentParser(
        description="G4.3 canary: token-count delta (system.md, tiktoken cl100k_base)."
    )
    parser.add_argument("--config", type=str, default=None)
    parser.add_argument("--baseline-prompt-version", type=str, required=True,
                        help="Baseline prompt version directory name (e.g. 17.202605042018)")
    parser.add_argument("--candidate-prompt-version", type=str, required=True,
                        help="Candidate prompt version directory name (e.g. 18.<timestamp>)")
    parser.add_argument("--prompt-base-path", type=str,
                        default="prompts/_base/scene_detail",
                        help="Base directory containing version subdirs")
    parser.add_argument("--exit-criteria-threshold", type=int, default=-1050,
                        help="token_delta upper bound for pass (default: -1050, R4-B4 carry)")
    # Pinning fields echoed for cross-canary consistency (G4.3 — 7 fields).
    parser.add_argument("--pid", type=str, default=None)
    parser.add_argument("--scene-index-list", type=str, default=None)
    parser.add_argument("--shot-index-list-per-scene", type=str, default=None)
    parser.add_argument("--model-routing", type=str, default=None)
    parser.add_argument("--prompt-source-mode", type=str, default=None,
                        choices=[None, "file", "db"])
    parser.add_argument("--card-commit-hash", type=str, default=None)
    parser.add_argument("--id-policy-card-snapshot-hash", type=str, default=None)
    parser.add_argument("--output", type=str, required=True)
    args = parser.parse_args()

    pinning = load_pinning_from_args(args)
    validate_pinning(pinning)
    # R6-I2 (G4.3 iter1): 본 스크립트는 prompt 파일만 읽고 CP 를 load 하지 않으므로
    # `id_policy_card_snapshot_hash` 값은 검증 없이 echo 만 한다 (operator 입력
    # 신뢰). 4 metric 스크립트들은 같은 hash 를 CP 에서 직접 산출/검증하므로,
    # token 스크립트의 echo 와 4 metric 의 산출 hash 가 다르면 analyzer 단계에서
    # 불일치 catch.

    try:
        import tiktoken  # type: ignore
    except ImportError:
        print(
            "[g4_3_token_count] ERROR: tiktoken not installed. "
            "Install via `pip install tiktoken` (or backend venv). NO silent fallback.",
            file=sys.stderr,
        )
        return 1

    encoder = tiktoken.get_encoding("cl100k_base")  # type: ignore[name-defined]

    base_dir = Path(args.prompt_base_path)
    baseline_path = base_dir / args.baseline_prompt_version / "system.md"
    candidate_path = base_dir / args.candidate_prompt_version / "system.md"

    # Validate prompt files exist BEFORE reading (silent-absorb 차단).
    if not baseline_path.exists():
        print(
            f"[g4_3_token_count] ERROR: baseline system.md not found: {baseline_path}",
            file=sys.stderr,
        )
        return 1
    if not candidate_path.exists():
        print(
            f"[g4_3_token_count] ERROR: candidate system.md not found: {candidate_path}",
            file=sys.stderr,
        )
        return 1

    baseline_text = baseline_path.read_text(encoding="utf-8")
    candidate_text = candidate_path.read_text(encoding="utf-8")

    baseline_token_count = len(encoder.encode(baseline_text))
    candidate_token_count = len(encoder.encode(candidate_text))
    # R4-B4: signed delta convention. Negative delta = reduction. Threshold
    # -1050 means candidate must be at least 1050 tokens shorter than baseline.
    token_delta = candidate_token_count - baseline_token_count
    exit_pass = token_delta <= args.exit_criteria_threshold

    out = {
        "timestamp": iso8601_now(),
        "pinning": pinning,
        "baseline_prompt_version": args.baseline_prompt_version,
        "candidate_prompt_version": args.candidate_prompt_version,
        "system_prompt_token_count_baseline": baseline_token_count,
        "system_prompt_token_count_candidate": candidate_token_count,
        "token_delta": token_delta,
        "exit_criteria_threshold": args.exit_criteria_threshold,
        "exit_criteria_pass": exit_pass,
        "measurement_tool": "tiktoken cl100k_base",
        "measurement_scripts": {
            "token_count": "scripts/canary/g4_3_token_count.py (system.md, tiktoken cl100k_base, R4-B4 signed)"
        },
    }

    if args.output == "-":
        json.dump(out, sys.stdout, ensure_ascii=False, indent=2, sort_keys=True)
        sys.stdout.write("\n")
    else:
        out_path = Path(args.output)
        out_path.parent.mkdir(parents=True, exist_ok=True)
        with out_path.open("w", encoding="utf-8") as fp:
            json.dump(out, fp, ensure_ascii=False, indent=2, sort_keys=True)

    print(
        f"[g4_3_token_count]\n"
        f"  baseline ({args.baseline_prompt_version}): {baseline_token_count} tokens\n"
        f"  candidate ({args.candidate_prompt_version}): {candidate_token_count} tokens\n"
        f"  token_delta: {token_delta}  (signed: candidate - baseline)\n"
        f"  threshold: {args.exit_criteria_threshold}  (pass iff token_delta <= threshold)\n"
        f"  exit_criteria_pass: {exit_pass}",
        file=sys.stderr,
    )
    return 0 if exit_pass else 1


if __name__ == "__main__":
    sys.exit(main())
