#!/usr/bin/env python3
"""
InsAIts -- Claude Code Post-Tool Hook
=====================================
This script runs automatically after every Claude Code tool use
(file write, bash command, etc). It inspects the output and
injects a corrective message into stdout if problems are detected.

Claude Code reads stdout from hooks and surfaces it back to the model.

SETUP:
  Add to your project's .claude/settings.json:
  {
    "hooks": {
      "PostToolUse": [
        {
          "matcher": ".*",
          "hooks": [
            {
              "type": "command",
              "command": "python -m insa_its.hooks.claude_code_hook"
            }
          ]
        }
      ]
    }
  }

  Or for specific tools only (e.g. only after Write):
  {
    "hooks": {
      "PostToolUse": [
        {
          "matcher": "Write|Edit|MultiEdit",
          "hooks": [{"type": "command", "command": "python -m insa_its.hooks.claude_code_hook"}]
        }
      ]
    }
  }

HOW IT WORKS:
  Claude Code passes tool result as JSON on stdin.
  This script inspects it and writes feedback to stdout.
  Claude Code feeds stdout back to the model automatically.
  Exit code 0 = pass through. Exit code 2 = block + show message.

Author: Cristi Bogdan -- Steddy Nova SRL / YuyAI
"""

import sys
import json
import os
from typing import Dict, List, Tuple

# Import from InsAIts SDK (adapted path)
try:
    from insa_its.ai_monitor import (
        BlankResponseDetector,
        IncompleteCodeDetector,
        WaitingForDescriptionDetector,
        TruncatedOutputDetector,
        ContextCollapseDetector,
        AIMonitor,
    )
    AVAILABLE = True
except ImportError:
    AVAILABLE = False


def extract_text_from_tool_result(data: Dict) -> Tuple[str, str]:
    """
    Extract the response text and task context from a Claude Code tool result.
    Returns (response_text, task_context).
    """
    response_text = ""
    task_context = ""

    # Claude Code hook payload structure
    tool_name = data.get("tool_name", "")
    tool_input = data.get("tool_input", {})
    tool_result = data.get("tool_response", {})

    # For Write/Edit tools -- inspect what was written
    if tool_name in ("Write", "Edit", "MultiEdit"):
        content = tool_input.get("content", "") or tool_input.get("new_string", "")
        path = tool_input.get("file_path", tool_input.get("path", ""))
        response_text = content
        task_context = f"Write file: {path}"

    # For Bash -- inspect command output
    elif tool_name == "Bash":
        output = ""
        if isinstance(tool_result, dict):
            output = tool_result.get("output", "") or tool_result.get("stdout", "")
        elif isinstance(tool_result, str):
            output = tool_result
        response_text = output
        task_context = f"Bash: {tool_input.get('command', '')[:80]}"

    # For generic assistant messages
    elif "content" in data:
        content = data["content"]
        if isinstance(content, list):
            texts = [b.get("text", "") for b in content if b.get("type") == "text"]
            response_text = "\n".join(texts)
        elif isinstance(content, str):
            response_text = content
        task_context = data.get("task", "")

    return response_text, task_context


def format_feedback(issues: List[Dict]) -> str:
    """Format InsAIts findings as a clear message for Claude Code."""
    lines = [
        "== InsAIts Quality Gate -- Issues Detected ==",
        "",
    ]
    for i, issue in enumerate(issues, 1):
        sev = issue.get("severity", "MEDIUM")
        itype = issue.get("type", "unknown").replace("_", " ").upper()
        desc = issue.get("description", "")
        fix = issue.get("intervention", "")
        lines += [
            f"{i}. [{sev}] {itype}",
            f"   What happened: {desc[:100]}",
            f"   Fix required:  {fix[:120]}",
            "",
        ]
    lines += [
        "-" * 56,
        "Address ALL issues above before completing this task.",
        "Call insaits_check tool after your fix to verify.",
    ]
    return "\n".join(lines)


def main() -> None:
    """Main entry point for the Claude Code PostToolUse hook."""
    # Read tool result from stdin (Claude Code passes it as JSON)
    raw = sys.stdin.read().strip()

    if not raw:
        sys.exit(0)  # Nothing to inspect -- pass through

    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        # Not JSON -- might be plain text output, inspect it directly
        data = {"content": raw, "task": ""}

    response_text, task_context = extract_text_from_tool_result(data)

    # Skip very short outputs (e.g. "OK", empty bash results)
    if len(response_text.strip()) < 10:
        sys.exit(0)

    # Skip binary or non-text content
    if not response_text.isprintable() and len(response_text) > 100:
        sys.exit(0)

    if not AVAILABLE:
        # InsAIts not installed -- log and pass through
        print("[InsAIts] Monitor not available. Install: pip install insa-its", file=sys.stderr)
        sys.exit(0)

    # Run inspection
    monitor = AIMonitor(model_id=os.environ.get("INSAITS_MODEL", "claude-opus"))
    result = monitor.inspect(response_text, task_context)

    if result.clean:
        # All good -- pass through silently
        if os.environ.get("INSAITS_VERBOSE"):
            print("[InsAIts] Response clean -- all checks passed.", file=sys.stderr)
        sys.exit(0)

    # Issues found -- format feedback and send to Claude Code
    issues = [
        {
            "type": a.anomaly_type.value,
            "severity": a.severity.value,
            "description": a.description,
            "intervention": a.intervention,
        }
        for a in result.anomalies
    ]

    feedback = format_feedback(issues)

    # Exit code 2 = Claude Code blocks the action and shows the message to Claude
    # Exit code 1 = logs to stderr only (non-blocking)
    # Exit code 0 = pass through

    has_critical = any(i["severity"] in ("CRITICAL", "HIGH") for i in issues)

    if has_critical:
        print(feedback)  # stdout -> Claude Code shows this to Claude
        sys.exit(2)      # block -- Claude must fix before continuing
    else:
        print(feedback, file=sys.stderr)  # stderr -> logged but not blocking
        sys.exit(0)


if __name__ == "__main__":
    main()
