"""A deterministic troubleshooting agent with bounded, testable tools."""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass, field

PLAYBOOKS = {
    "network": "Check the timeout, retry transient failures with backoff, and log the final status code.",
    "dependency": "Recreate the environment, pin the dependency, and record the failing version.",
    "general": "Reproduce the issue with the smallest input, capture the error, and add a regression test.",
}


@dataclass(frozen=True)
class Action:
    name: str
    arguments: dict[str, str] = field(default_factory=dict)


@dataclass
class Task:
    request: str
    observations: list[str] = field(default_factory=list)
    trace: list[str] = field(default_factory=list)
    answer: str | None = None


def classify_issue(text: str) -> str:
    lowered = text.lower()
    if any(word in lowered for word in ("timeout", "network", "api")):
        return "network"
    if any(word in lowered for word in ("import", "package", "dependency")):
        return "dependency"
    return "general"


def lookup_playbook(category: str) -> str:
    if category not in PLAYBOOKS:
        raise ValueError(f"Unknown category: {category}")
    return PLAYBOOKS[category]


TOOLS: dict[str, Callable[..., str]] = {
    "classify_issue": classify_issue,
    "lookup_playbook": lookup_playbook,
}

EXPECTED_ARGUMENTS = {
    "classify_issue": {"text"},
    "lookup_playbook": {"category"},
}


def decide(task: Task) -> Action:
    """Stand in for a model so every action stays free and deterministic."""
    if len(task.observations) == 0:
        return Action("classify_issue", {"text": task.request})
    if len(task.observations) == 1:
        return Action("lookup_playbook", {"category": task.observations[0]})
    return Action("finish", {"answer": task.observations[-1]})


def execute(action: Action) -> str:
    tool = TOOLS.get(action.name)
    if tool is None:
        raise ValueError(f"Unknown tool: {action.name}")
    if set(action.arguments) != EXPECTED_ARGUMENTS[action.name]:
        raise ValueError(f"Invalid arguments for {action.name}")
    return tool(**action.arguments)


def run_agent(
    task: Task,
    planner: Callable[[Task], Action] = decide,
    max_steps: int = 3,
) -> Task:
    """Validate, execute, observe, trace, and stop within a fixed budget."""
    if max_steps < 1:
        raise ValueError("max_steps must be at least 1")

    for step in range(1, max_steps + 1):
        action = planner(task)
        task.trace.append(f"{step}. {action.name}")

        if action.name == "finish":
            if set(action.arguments) != {"answer"}:
                raise ValueError("Invalid finish action")
            task.answer = action.arguments["answer"]
            return task

        task.observations.append(execute(action))

    raise RuntimeError(f"Agent exceeded the {max_steps}-step budget")


if __name__ == "__main__":
    result = run_agent(Task("My API request times out"))
    print("\n".join(result.trace))
    print(f"\nAnswer: {result.answer}")
