#!/usr/bin/env python3
"""
asker-label: C-2b judgment pipeline seam.

Composes a label row by validating inputs, calling model-resolve, and invoking
the C-2a chokepoint (label-append).
"""

import json
import sys
import subprocess
import hashlib
import argparse
from pathlib import Path


VERDICT_ENUM = {"good", "partial", "bad", "error"}


def main():
    parser = argparse.ArgumentParser(
        description="Compose and write a label row",
        add_help=False
    )
    parser.add_argument("--labels-file", required=True, metavar="PATH",
                        help="Path to labels JSONL file")
    parser.add_argument("--reply", required=True, metavar="FILE",
                        help="Reply message JSON file being judged")
    parser.add_argument("--ask-id", required=True, type=int, metavar="N",
                        help="Room message id of the ask")
    parser.add_argument("--author", required=True, metavar="NICK",
                        help="Author nick (labeling agent)")
    parser.add_argument("--verdict", required=True, metavar="VERDICT",
                        help="Label value: good|partial|bad|error")
    parser.add_argument("--judge-prompt", default=None, metavar="FILE",
                        help="Rendered judge prompt file")
    parser.add_argument("--template-version", default=None, metavar="VER",
                        help="Template version (e.g. v1)")
    parser.add_argument("--model-nicks", default="coder", metavar="CSV",
                        help="Comma-separated nicks expected to carry model marker")
    parser.add_argument("--label-append", default="label-append", metavar="BIN",
                        help="Path to label-append chokepoint")
    parser.add_argument("--model-resolve", default="model-resolve", metavar="BIN",
                        help="Path to model-resolve")

    try:
        args = parser.parse_args()
    except SystemExit:
        print("asker-label: error: argument parsing failed", file=sys.stderr)
        sys.exit(2)

    # === Step 1: Verdict domain check ===
    if args.verdict not in VERDICT_ENUM:
        print(f"asker-label: error: verdict '{args.verdict}' not in {VERDICT_ENUM}", file=sys.stderr)
        sys.exit(2)

    # === Step 2: Judge prompt / template version pairing ===
    has_prompt = args.judge_prompt is not None
    has_template = args.template_version is not None

    if has_prompt != has_template:
        print("asker-label: error: --judge-prompt and --template-version must both be present or both absent", file=sys.stderr)
        sys.exit(2)

    prompt_hash = None
    template_version = None

    if has_prompt and has_template:
        # Compute SHA-256 of the raw bytes of the prompt file
        try:
            with open(args.judge_prompt, 'rb') as f:
                prompt_bytes = f.read()
            prompt_hash = hashlib.sha256(prompt_bytes).hexdigest()
            template_version = args.template_version
        except (FileNotFoundError, IOError) as e:
            print(f"asker-label: error: cannot read judge-prompt {args.judge_prompt}: {e}", file=sys.stderr)
            sys.exit(2)

    # === Step 3: Call model-resolve ===
    try:
        mr_result = subprocess.run(
            [args.model_resolve, "--reply", args.reply, "--model-nicks", args.model_nicks],
            capture_output=True,
            text=True,
            check=False
        )
    except (FileNotFoundError, OSError) as e:
        print(f"asker-label: error: cannot run model-resolve: {e}", file=sys.stderr)
        sys.exit(2)

    if mr_result.returncode != 0:
        # Pass through model-resolve's stderr and exit code
        sys.stderr.write(mr_result.stderr)
        sys.exit(mr_result.returncode)

    try:
        mr_output = json.loads(mr_result.stdout)
    except json.JSONDecodeError as e:
        print(f"asker-label: error: model-resolve output is not valid JSON: {e}", file=sys.stderr)
        sys.exit(2)

    model_id = mr_output.get("model_id")
    model_id_reason = mr_output.get("model_id_reason")

    # === Step 4: Extract verified from reply ===
    try:
        with open(args.reply, 'r') as f:
            reply = json.load(f)
    except (FileNotFoundError, IOError, json.JSONDecodeError) as e:
        print(f"asker-label: error: cannot read reply {args.reply}: {e}", file=sys.stderr)
        sys.exit(2)

    verified = reply.get("verified") is True

    # === Step 5: Invoke label-append chokepoint ===
    la_args = [
        args.label_append,
        "--file", args.labels_file,
        "--label", args.verdict,
        "--author", args.author,
        "--verified", "true" if verified else "false",
        "--ask-id", str(args.ask_id),
    ]

    # Add model_id or model_reason (XOR)
    if model_id is not None:
        la_args.extend(["--model-id", model_id])
    else:
        la_args.extend(["--model-reason", model_id_reason])

    # Add prompt-hash and template-version if both present
    if prompt_hash is not None:
        la_args.extend(["--prompt-hash", prompt_hash])
        la_args.extend(["--template-version", template_version])

    try:
        la_result = subprocess.run(
            la_args,
            capture_output=True,
            text=True,
            check=False
        )
    except (FileNotFoundError, OSError) as e:
        print(f"asker-label: error: cannot run label-append: {e}", file=sys.stderr)
        sys.exit(2)

    # === Step 6: Handle chokepoint exit codes and idempotency ===
    if la_result.returncode == 0:
        # Success: print stdout row
        sys.stdout.write(la_result.stdout)
        sys.exit(0)

    # Check if it's a duplicate error (exit 1 with E_DUP in stderr)
    if la_result.returncode == 1 and "E_DUP" in la_result.stderr:
        # Extract ask_id and author from stderr or from our args
        print(f"asker-label: duplicate ask={args.ask_id} author={args.author} (noop)", file=sys.stderr)
        sys.exit(0)

    # Other errors
    if la_result.returncode == 1:
        # Pass through chokepoint's stderr
        sys.stderr.write(la_result.stderr)
        sys.exit(1)

    if la_result.returncode == 2:
        # Structural failure
        sys.stderr.write(la_result.stderr)
        sys.exit(2)

    # Unexpected exit code
    sys.stderr.write(la_result.stderr)
    sys.exit(la_result.returncode)


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