import json

import pytest

import protocol
from protocol import Final, Malformed, ToolCall

TOOLS = [
    {"name": "run_command", "description": "run a shell command",
     "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}}},
    {"type": "function", "function": {"name": "read_file", "description": "read a file",
                                      "parameters": {"type": "object"}}},
]


def block(payload: dict) -> str:
    return json.dumps(payload)


def test_one_block_is_a_tool_call():
    reply = protocol.parse_reply(
        [block({"tool": "run_command", "arguments": {"command": "ls"}})], TOOLS)
    assert reply == ToolCall("run_command", {"command": "ls"})


def test_final_block_is_a_final_answer():
    assert protocol.parse_reply([block({"tool": "final", "text": "8"})], TOOLS) == Final("8")


def test_no_block_is_the_plain_prose():
    assert protocol.parse_reply([], TOOLS, text="just talking") == Final("just talking")


def test_two_blocks_are_malformed():
    reply = protocol.parse_reply([block({"tool": "final", "text": "a"})] * 2, TOOLS)
    assert isinstance(reply, Malformed) and "got 2" in reply.reason


def test_invalid_json_is_malformed():
    assert isinstance(protocol.parse_reply(["{not json"], TOOLS), Malformed)


def test_unknown_tool_is_malformed():
    reply = protocol.parse_reply([block({"tool": "rm_rf", "arguments": {}})], TOOLS)
    assert isinstance(reply, Malformed) and "rm_rf" in reply.reason


def test_missing_arguments_object_is_malformed():
    reply = protocol.parse_reply([block({"tool": "run_command"})], TOOLS)
    assert isinstance(reply, Malformed)


def test_final_without_text_is_malformed():
    assert isinstance(protocol.parse_reply([block({"tool": "final"})], TOOLS), Malformed)


@pytest.mark.parametrize("needle", ['{"tool": "final", "text": "<answer>"}',
                                    "EXACTLY ONE fenced json block"])
def test_preamble_pins_the_wire_shape(needle):
    assert needle in protocol.render_preamble("be terse", TOOLS)


def test_preamble_carries_every_tool_and_the_system_prompt():
    text = protocol.render_preamble("be terse", TOOLS)
    assert "run_command" in text and "read_file" in text
    assert text.rstrip().endswith("be terse")


def test_tool_names_reads_both_tool_shapes():
    assert protocol.tool_names(TOOLS) == {"run_command", "read_file"}
