"""Tests para run_claude_tool_loop — usa fake de stream_anthropic_messages, sin HTTP."""

import sys
import os
import json

sys.path.insert(0, os.path.dirname(__file__))


# ---------------------------------------------------------------------------
# Fake de stream_anthropic_messages inyectado via monkeypatch
# ---------------------------------------------------------------------------

def _make_text_response(text: str, stop_reason: str = "end_turn"):
    """Genera la secuencia de eventos de una respuesta de texto pura."""
    for ch in text:
        yield {"type": "text", "text": ch}
    yield {"type": "done", "stop_reason": stop_reason}


def _make_tool_use_response(tool_id: str, tool_name: str, tool_input: dict):
    """Genera la secuencia de eventos de un tool_use."""
    yield {"type": "tool_use_start", "id": tool_id, "name": tool_name, "index": 0}
    raw = json.dumps(tool_input)
    # Simula streaming del JSON en dos partes
    mid = len(raw) // 2
    yield {"type": "tool_input_delta", "index": 0, "partial_json": raw[:mid]}
    yield {"type": "tool_input_delta", "index": 0, "partial_json": raw[mid:]}
    yield {"type": "done", "stop_reason": "tool_use"}


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

def test_tool_use_seguido_de_end_turn_ejecuta_dispatch_y_termina(monkeypatch):
    import run_claude_tool_loop as mod

    call_count = {"n": 0}

    def fake_stream(messages, model, system, tools, max_tokens):
        call_count["n"] += 1
        if call_count["n"] == 1:
            # Primera llamada: el modelo pide la tool "add"
            yield from _make_tool_use_response("toolu_01", "add", {"a": 3, "b": 4})
        else:
            # Segunda llamada: el modelo responde con el resultado
            yield from _make_text_response("El resultado es 7.")

    monkeypatch.setattr(mod, "stream_anthropic_messages", fake_stream)

    dispatch_called = {"input": None}
    def add_tool(inp):
        dispatch_called["input"] = inp
        return inp["a"] + inp["b"]

    messages = [{"role": "user", "content": "cuanto es 3 + 4?"}]
    result = mod.run_claude_tool_loop(
        messages=messages,
        tools=[{"name": "add", "description": "suma", "input_schema": {}}],
        dispatch={"add": add_tool},
        model="claude-haiku-4-5-20251001",
    )

    assert result["stop_reason"] == "end_turn"
    assert result["iterations"] == 2
    assert result["final_text"] == "El resultado es 7."
    assert dispatch_called["input"] == {"a": 3, "b": 4}

    # Verifica que se anado el tool_result al historial
    roles = [m["role"] for m in result["messages"]]
    assert roles.count("assistant") == 2
    assert roles.count("user") >= 2

    # El mensaje user con tool_result debe tener is_error=False (ausente)
    tool_result_msg = next(
        m for m in result["messages"]
        if m["role"] == "user" and isinstance(m["content"], list)
        and m["content"] and m["content"][0].get("type") == "tool_result"
    )
    assert tool_result_msg["content"][0].get("is_error") is None or \
           tool_result_msg["content"][0].get("is_error") is False


def test_tool_dispatch_que_falla_devuelve_is_error_True_en_tool_result(monkeypatch):
    import run_claude_tool_loop as mod

    call_count = {"n": 0}

    def fake_stream(messages, model, system, tools, max_tokens):
        call_count["n"] += 1
        if call_count["n"] == 1:
            yield from _make_tool_use_response("toolu_02", "divide", {"a": 10, "b": 0})
        else:
            yield from _make_text_response("No se puede dividir por cero.")

    monkeypatch.setattr(mod, "stream_anthropic_messages", fake_stream)

    def divide_tool(inp):
        return inp["a"] / inp["b"]  # ZeroDivisionError

    messages = [{"role": "user", "content": "divide 10 entre 0"}]
    result = mod.run_claude_tool_loop(
        messages=messages,
        tools=[{"name": "divide", "description": "division", "input_schema": {}}],
        dispatch={"divide": divide_tool},
    )

    # Debe haber un tool_result con is_error=True
    tool_result_msg = next(
        m for m in result["messages"]
        if m["role"] == "user" and isinstance(m["content"], list)
        and m["content"] and m["content"][0].get("type") == "tool_result"
    )
    assert tool_result_msg["content"][0].get("is_error") is True
    assert "Error" in tool_result_msg["content"][0]["content"]


def test_tool_desconocido_devuelve_is_error_True(monkeypatch):
    import run_claude_tool_loop as mod

    call_count = {"n": 0}

    def fake_stream(messages, model, system, tools, max_tokens):
        call_count["n"] += 1
        if call_count["n"] == 1:
            yield from _make_tool_use_response("toolu_03", "nonexistent_tool", {"x": 1})
        else:
            yield from _make_text_response("No pude usar esa tool.")

    monkeypatch.setattr(mod, "stream_anthropic_messages", fake_stream)

    messages = [{"role": "user", "content": "usa nonexistent_tool"}]
    result = mod.run_claude_tool_loop(
        messages=messages,
        tools=[],
        dispatch={},  # dispatch vacio — tool no existe
    )

    tool_result_msg = next(
        m for m in result["messages"]
        if m["role"] == "user" and isinstance(m["content"], list)
        and m["content"] and m["content"][0].get("type") == "tool_result"
    )
    assert tool_result_msg["content"][0].get("is_error") is True
    assert "not found" in tool_result_msg["content"][0]["content"]


def test_on_text_callback_recibe_deltas_de_texto(monkeypatch):
    import run_claude_tool_loop as mod

    def fake_stream(messages, model, system, tools, max_tokens):
        yield from _make_text_response("hola mundo")

    monkeypatch.setattr(mod, "stream_anthropic_messages", fake_stream)

    received = []
    messages = [{"role": "user", "content": "saluda"}]
    result = mod.run_claude_tool_loop(
        messages=messages,
        tools=[],
        dispatch={},
        on_text=lambda d: received.append(d),
    )

    assert "".join(received) == "hola mundo"
    assert result["final_text"] == "hola mundo"


def test_max_iters_limita_el_numero_de_iteraciones(monkeypatch):
    import run_claude_tool_loop as mod

    # Siempre responde con tool_use para forzar loop infinito
    def fake_stream_infinite(messages, model, system, tools, max_tokens):
        yield from _make_tool_use_response("toolu_inf", "noop", {})

    monkeypatch.setattr(mod, "stream_anthropic_messages", fake_stream_infinite)

    messages = [{"role": "user", "content": "loop"}]
    result = mod.run_claude_tool_loop(
        messages=messages,
        tools=[{"name": "noop", "description": "no-op", "input_schema": {}}],
        dispatch={"noop": lambda inp: "ok"},
        max_iters=3,
    )

    assert result["iterations"] == 3
    assert result["stop_reason"] == "max_iters"
