#!/usr/bin/env python3
"""Fail-closed probes for provider wrappers and assembled story hygiene."""
from __future__ import annotations

import argparse
import hashlib
import importlib
import inspect
import json
import re
from pathlib import Path

PLACEHOLDERS = [
    re.compile(r"P\d+D\d+L\d+"),
    re.compile(r"\b(?:TODO|FIXME|PLACEHOLDER)\b", re.I),
]


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


def provider_probe(module_name: str, class_name: str, inputs_json: str) -> dict:
    cls = getattr(importlib.import_module(module_name), class_name)
    constructor = str(inspect.signature(cls))
    obj = cls()
    methods = {}
    for name in dir(obj):
        if name.startswith("_"):
            continue
        value = getattr(obj, name)
        if callable(value):
            try:
                methods[name] = str(inspect.signature(value))
            except (TypeError, ValueError):
                methods[name] = "unknown"
    result = {
        "status": "passed",
        "module": module_name,
        "class": class_name,
        "constructor": constructor,
        "public_methods": methods,
    }
    if hasattr(obj, "get_info"):
        result["info"] = obj.get_info()
    if inputs_json:
        inputs = json.loads(inputs_json)
        if not hasattr(obj, "dry_run"):
            raise SystemExit("provider has no dry_run; do not make a paid call from this probe")
        result["dry_run"] = obj.dry_run(inputs)
    return result


def story_probe(path: Path) -> dict:
    data = json.loads(path.read_text())
    parts = data.get("parts", [])
    paragraphs = [
        (part.get("part"), index, text)
        for part in parts
        for index, text in enumerate(part.get("paragraphs", []), 1)
    ]
    hits = []
    for part, index, text in paragraphs:
        for pattern in PLACEHOLDERS:
            for match in pattern.finditer(text):
                hits.append({
                    "part": part,
                    "paragraph": index,
                    "pattern": pattern.pattern,
                    "match": match.group(0),
                })
    result = {
        "status": "passed" if not hits else "failed",
        "path": str(path),
        "sha256": sha256(path),
        "parts": len(parts),
        "paragraphs": len(paragraphs),
        "placeholder_hits": hits,
        "semantic_review_required": True,
        "note": "Zero markers does not certify prose. Read each changed paragraph and adjacent transitions.",
    }
    return result


def main() -> None:
    parser = argparse.ArgumentParser()
    sub = parser.add_subparsers(dest="command", required=True)
    provider = sub.add_parser("provider")
    provider.add_argument("module")
    provider.add_argument("class_name")
    provider.add_argument("--inputs-json", default="")
    story = sub.add_parser("story")
    story.add_argument("path", type=Path)
    args = parser.parse_args()
    if args.command == "provider":
        result = provider_probe(args.module, args.class_name, args.inputs_json)
    else:
        result = story_probe(args.path)
    print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
    if result["status"] != "passed":
        raise SystemExit(1)


if __name__ == "__main__":
    main()
