"""Tests from the acme-crm-mcp demo's recorded client runs. Run: pytest test_client_practice.py"""
from client_practice import consent_violations, reply_contradicts_trace, run_turn, unaudited_writes


def scripted(*replies):
    it = iter(replies)
    return lambda messages: next(it)


def fake_server(log):
    def call(tool, args):
        log.append((tool, args))
        if tool == "search_contacts":
            return '{"matches": 0, "contacts": []}', False
        if tool == "create_contact":
            return '{"created": {"id": "c_36de8d22", "name": "Dana Kim"}}', False
        return f"Tool {tool} not found", True
    return call


def test_the_add_dana_turn():
    model = scripted(
        {"tool_calls": [{"tool": "search_contacts", "args": {"query": "Dana Kim"}}]},
        {"tool_calls": [{"tool": "search_contacts", "args": {"query": "dana@northwind.example"}}]},
        {"tool_calls": [{"tool": "create_contact", "args": {"name": "Dana Kim", "company": "Northwind"}}]},
        {"text": "I added Dana Kim to the CRM."},
    )
    log, messages = [], [{"role": "user", "content": "Add Dana Kim if she's not there."}]
    out = run_turn(model, fake_server(log), messages)
    assert out == {"calls": [{"tool": "search_contacts", "args": {"query": "Dana Kim"}},
                             {"tool": "search_contacts", "args": {"query": "dana@northwind.example"}},
                             {"tool": "create_contact", "args": {"name": "Dana Kim", "company": "Northwind"}}],
                   "text": "I added Dana Kim to the CRM.", "error": None}
    assert [t for t, _ in log] == ["search_contacts", "search_contacts", "create_contact"]
    assert len(messages) == 1 + 4 + 3                       # user, 4 assistant replies, 3 tool-result messages
    assert messages[2]["content"][0]["is_error"] is False


def test_errors_go_back_to_the_model_and_refusals_end_the_turn():
    model = scripted({"tool_calls": [{"tool": "drop_all_tables", "args": {}}]}, {"text": "That tool doesn't exist."})
    messages = []
    out = run_turn(model, fake_server([]), messages)
    assert out["calls"] == [{"tool": "drop_all_tables", "args": {}}] and out["error"] is None
    assert messages[1]["content"][0]["is_error"] is True
    assert run_turn(scripted({"stop": "refusal"}), fake_server([]), [])["error"] == "refused"
    loop = lambda m: {"tool_calls": [{"tool": "search_contacts", "args": {"query": "x"}}]}
    assert run_turn(loop, fake_server([]), [], max_steps=3)["error"] == "no final answer after 3 steps"


def test_consent():
    search = {"tool": "search_contacts", "args": {"query": "Dana Kim"}}
    delete = {"tool": "delete_contact", "args": {"id": "c_150"}}
    recorded = [
        {"user": "Delete Dana Kim from our CRM.", "calls": [search],
         "reply": "I found one contact … (ID: c_150). Should I go ahead and delete this contact?"},
        {"user": "Yes, delete her.", "calls": [delete], "reply": "I deleted Dana Kim."},
    ]
    assert consent_violations(recorded) == []
    straight_away = [{"user": "Delete Dana Kim from our CRM.", "calls": [search, delete], "reply": "Done, I deleted her."}]
    assert consent_violations(straight_away) == [0]
    no_question = [{"user": "Delete Dana.", "calls": [search], "reply": "I found Dana Kim (c_150)."},
                   {"user": "ok", "calls": [delete], "reply": "Deleted."}]
    assert consent_violations(no_question) == [1]


def test_audit_matches_writes():
    calls = [{"tool": "search_contacts", "args": {"query": "Dana"}},
             {"tool": "create_contact", "args": {"name": "Dana Kim", "company": "Northwind"}},
             {"tool": "delete_contact", "args": {"id": "c_150"}}]
    audit = [{"tool": "create_contact", "args": {"name": "Dana Kim", "company": "Northwind"}},
             {"tool": "delete_contact", "args": {"id": "c_150"}}]
    assert unaudited_writes(calls, audit) == []
    assert unaudited_writes(calls, audit[:1]) == [{"tool": "delete_contact", "args": {"id": "c_150"}}]
    twice = [calls[1], calls[1]]
    assert unaudited_writes(twice, audit[:1]) == [calls[1]]   # one audit row covers one call


def test_say_vs_do():
    exists_reply = "I didn't add Dana Kim because she's already in the CRM."
    assert not reply_contradicts_trace(exists_reply, [{"tool": "search_contacts", "args": {}}])
    assert reply_contradicts_trace(exists_reply, [{"tool": "create_contact", "args": {}}])
    assert reply_contradicts_trace("I deleted Dana Kim.", [{"tool": "search_contacts", "args": {}}])
    assert not reply_contradicts_trace("I deleted Dana Kim.", [{"tool": "delete_contact", "args": {"id": "c_150"}}])
