"""PostToolUse hook that injects Phase 3 directives after test runs.

Mirrors ts-agent's ``test-prune-hook.ts``: after every pytest execution the
hook parses pass/fail counts and tells the agent whether to stop, prune, or
repair.

When all tests pass (DONE), the hook sets a shared ``asyncio.Event`` that
``run_agent()`` uses to break out of the query loop and terminate the session.
"""

from __future__ import annotations

import asyncio
import json
import logging
import re
import sys
from typing import Any

from claude_code_sdk import HookContext
from claude_code_sdk.types import HookJSONOutput

from py_agent.services.pytest_output_parser import parse_pytest_output

logger = logging.getLogger(__name__)

TEST_COMMAND_PATTERNS: list[re.Pattern[str]] = [
    re.compile(r"\bpytest\b"),
    re.compile(r"python\s+-m\s+pytest"),
]

PRUNE_FALLBACK_MESSAGE = (
    "REMINDER — Follow your Phase 3 decision tree exactly:\n"
    "- ALL tests green → STOP immediately.\n"
    "- 6+ tests green but some are not → Phase 5 (PRUNE): delete failing tests, "
    "run lintCommand --fix, formatCommand, tests once, then STOP.\n"
    "- Fewer than 6 green → Phase 4 (REPAIR), then back to Phase 3."
)


def _emit(event: dict[str, Any]) -> None:
    """Write one JSON event to stdout for early-cli consumption."""
    print(json.dumps(event), file=sys.stdout, flush=True)


def _is_test_command(command: str) -> bool:
    return any(p.search(command) for p in TEST_COMMAND_PATTERNS)


def build_directive(passed: int, failed: int) -> str:
    """Build a Phase 3 decision directive from test counts."""
    if failed == 0 and passed > 0:
        return (
            f"ALL {passed} TESTS PASSED. "
            "STOP IMMEDIATELY. Do not run any more commands. You are DONE."
        )

    if passed >= 6:
        return (
            f"{passed} passed, {failed} failed. "
            f"Phase 5 (PRUNE): Delete ALL {failed} failing test(s) now. "
            "Do NOT attempt to fix them. After pruning, run lintCommand "
            "(with --fix), formatCommand, then run tests once to confirm. "
            "Then STOP."
        )

    return (
        f"{passed} passed, {failed} failed. "
        "Phase 4 (REPAIR): Read the error output and fix the failing tests, "
        "then return to Phase 3."
    )


def _get_tool_response_output(input_data: dict[str, Any]) -> str:
    """Extract text output from the tool response."""
    response = input_data.get("tool_response")

    if isinstance(response, str):
        return response

    if isinstance(response, dict):
        # stdout + stderr (Bash tool)
        stdout = response.get("stdout", "") or ""
        stderr = response.get("stderr", "") or ""
        combined = f"{stdout}\n{stderr}".strip()
        if combined:
            return combined

        # output field
        if "output" in response:
            return str(response["output"])

        # content blocks
        content = response.get("content")
        if isinstance(content, str):
            return content
        if isinstance(content, list):
            return "\n".join(
                block.get("text", "")
                for block in content
                if isinstance(block, dict) and block.get("type") == "text"
            )

    if isinstance(response, list):
        return "\n".join(
            block.get("text", "")
            for block in response
            if isinstance(block, dict) and block.get("type") == "text"
        )

    return ""


def create_post_tool_hook(
    done_event: asyncio.Event,
    test_counts: dict[str, int],
) -> Any:  # HookCallback
    """Create a PostToolUse hook that sets *done_event* when all tests pass."""

    async def post_tool_use_hook(
        input_data: dict[str, Any],
        tool_use_id: str | None,
        context: HookContext,
    ) -> HookJSONOutput:
        if input_data.get("hook_event_name") != "PostToolUse":
            return HookJSONOutput()

        tool_input = input_data.get("tool_input", {})
        command = tool_input.get("command", "") if isinstance(tool_input, dict) else ""
        if not _is_test_command(command):
            return HookJSONOutput()

        output = _get_tool_response_output(input_data)
        result = parse_pytest_output(output)

        if result is None:
            logger.info("Prune hook: pytest output not parseable, injecting fallback reminder")
            return HookJSONOutput(
                hookSpecificOutput={
                    "hookEventName": input_data.get("hook_event_name"),
                    "additionalContext": PRUNE_FALLBACK_MESSAGE,
                }
            )

        passed, failed = result
        test_counts["passed"] = passed
        test_counts["failed"] = failed
        action = "DONE" if failed == 0 else ("PRUNE" if passed >= 6 else "REPAIR")
        logger.info(f"Prune hook: {passed} passed, {failed} failed → {action}")

        _emit({"type": "agent_hook", "action": action, "passed": passed, "failed": failed})

        if action == "DONE":
            _emit({"type": "agent_hook", "action": "STOPPING", "passed": passed, "failed": 0})
            done_event.set()

        directive = build_directive(passed, failed)

        return HookJSONOutput(
            hookSpecificOutput={
                "hookEventName": input_data.get("hook_event_name"),
                "additionalContext": directive,
            }
        )

    return post_tool_use_hook
