"""Mock failure injection — intercepts tool calls to simulate server failures.

When an eval case specifies mock_failures, this module wraps the ServerManager
to inject errors before the real tool call reaches the server.
"""

from __future__ import annotations

from dataclasses import dataclass, field

from hatchdx.agent.eval.models import MockFailure
from hatchdx.agent.runtime.loop import ToolCallEvent
from hatchdx.agent.runtime.tools import ToolRoute


# ---------------------------------------------------------------------------
# Failure state tracking
# ---------------------------------------------------------------------------


@dataclass
class _FailureState:
    """Tracks how many times a mock failure has been triggered."""

    failure: MockFailure
    trigger_count: int = 0

    def should_fail(self) -> bool:
        """Return True if this failure should trigger on the next call."""
        if self.failure.fail_count == 0:
            # Always fail
            return True
        return self.trigger_count < self.failure.fail_count

    def record_trigger(self) -> None:
        self.trigger_count += 1


# ---------------------------------------------------------------------------
# Mock failure interceptor
# ---------------------------------------------------------------------------


class MockFailureInterceptor:
    """Intercepts tool calls and injects failures based on eval case config.

    Usage::

        interceptor = MockFailureInterceptor(mock_failures)

        # Before each tool call execution:
        event = interceptor.maybe_intercept(tool_call_name, route)
        if event is not None:
            # Use this error event instead of executing the real call
            ...
    """

    def __init__(self, mock_failures: list[MockFailure]) -> None:
        self._states: list[_FailureState] = [
            _FailureState(failure=mf) for mf in mock_failures
        ]

    def maybe_intercept(
        self,
        tool_name: str,
        route: ToolRoute,
        arguments: dict,
    ) -> ToolCallEvent | None:
        """Check if this tool call should be intercepted with a mock failure.

        Args:
            tool_name: The exposed tool name being called.
            route: The routing info for this tool.
            arguments: The tool call arguments.

        Returns:
            A ToolCallEvent with the error if intercepted, None otherwise.
        """
        for state in self._states:
            if not self._matches(state.failure, route):
                continue

            if not state.should_fail():
                continue

            state.record_trigger()
            return ToolCallEvent(
                tool_name=tool_name,
                server_name=route.server_name,
                arguments=arguments,
                result=state.failure.error,
                is_error=True,
                latency_ms=0.0,
            )

        return None

    @staticmethod
    def _matches(failure: MockFailure, route: ToolRoute) -> bool:
        """Check if a mock failure config matches a given route."""
        if failure.server != route.server_name:
            return False
        if failure.tools and route.original_name not in failure.tools:
            return False
        return True

    @property
    def has_failures(self) -> bool:
        """Return True if there are any mock failures configured."""
        return len(self._states) > 0
