"""Tests para react_loop."""

from react_loop import react_loop


def _make_llm(responses: list[str]) -> object:
    """Return a llm_chat callable that yields responses in order."""
    it = iter(responses)

    def llm_chat(messages: list[dict]) -> str:
        return next(it)

    return llm_chat


def test_agente_que_usa_1_tool_y_da_respuesta_final():
    called_with = {}

    def search(query: str) -> str:
        called_with["query"] = query
        return "Paris"

    llm_responses = [
        "Thought: I need to search for the capital.\nAction: search\nAction Input: {\"query\": \"capital of France\"}\n",
        "Thought: I have the answer.\nFinal Answer: The capital of France is Paris.",
    ]

    result = react_loop(
        llm_chat=_make_llm(llm_responses),
        tools={"search": search},
        system_prompt="You are a helpful assistant.",
        user_prompt="What is the capital of France?",
    )

    assert result == "The capital of France is Paris."
    assert called_with.get("query") == "capital of France"


def test_agente_que_usa_multiples_tools_iterativamente():
    call_log = []

    def lookup(entity: str) -> str:
        call_log.append(("lookup", entity))
        return f"Info about {entity}"

    def summarize(text: str) -> str:
        call_log.append(("summarize", text))
        return "Summary: " + text[:20]

    llm_responses = [
        "Thought: Lookup first.\nAction: lookup\nAction Input: {\"entity\": \"Python\"}",
        "Thought: Now summarize.\nAction: summarize\nAction Input: {\"text\": \"Info about Python\"}",
        "Final Answer: Done.",
    ]

    result = react_loop(
        llm_chat=_make_llm(llm_responses),
        tools={"lookup": lookup, "summarize": summarize},
        system_prompt="You are an assistant.",
        user_prompt="Tell me about Python.",
        max_iterations=5,
    )

    assert result == "Done."
    assert len(call_log) == 2
    assert call_log[0] == ("lookup", "Python")
    assert call_log[1] == ("summarize", "Info about Python")


def test_agente_que_da_respuesta_directa_sin_tools():
    llm_responses = [
        "Final Answer: 42 is the answer.",
    ]

    result = react_loop(
        llm_chat=_make_llm(llm_responses),
        tools={},
        system_prompt="You are a calculator.",
        user_prompt="What is 6*7?",
    )

    assert result == "42 is the answer."


def test_max_iterations_alcanzado_fallback():
    call_count = {"n": 0}

    def noop(q: str) -> str:
        return "nothing"

    def llm_chat(messages: list[dict]) -> str:
        call_count["n"] += 1
        return "Thought: Thinking...\nAction: noop\nAction Input: {\"q\": \"x\"}"

    result = react_loop(
        llm_chat=llm_chat,
        tools={"noop": noop},
        system_prompt=".",
        user_prompt="Loop forever.",
        max_iterations=3,
    )

    assert call_count["n"] == 3
    assert "Thought: Thinking" in result


def test_tool_que_falla_error_handling():
    observations = []

    def broken_tool(x: str) -> str:
        raise ValueError("tool exploded")

    llm_responses = [
        "Action: broken_tool\nAction Input: {\"x\": \"test\"}",
        "Final Answer: Tool failed gracefully.",
    ]

    result = react_loop(
        llm_chat=_make_llm(llm_responses),
        tools={"broken_tool": broken_tool},
        system_prompt=".",
        user_prompt="Use broken tool.",
        on_observation=lambda obs: observations.append(obs),
    )

    assert result == "Tool failed gracefully."
    assert any("Error" in obs for obs in observations)
