"""Tests on Lab 10's recorded runs (gpt-5.4-nano). T06: "My trekking poles never arrived", passed from
triage to the orders agent in each of the three formats. The messages are the real ones the next agent was
given, replayed from the lab's cache. The run outcomes are all 270 team runs (30 tasks × 3 runs × 3 formats).
Run: pytest test_handoff_practice.py"""
import json

from handoff_practice import answered_at_once, next_input, ping_pong, render_log, repeated_lookups

CUSTOMER = "Hi, grace.kim@example.com. My trekking poles never arrived and it's been weeks."
# Triage's own messages after the customer's: a lookup, its result, the transfer, its result.
TRIAGE = json.loads("[{\"role\": \"assistant\", \"content\": null, \"tool_calls\": [{\"id\": \"call_TwwIWARdpqNmyYghfonMBRRP\", \"type\": \"function\", \"function\": {\"name\": \"find_customer\", \"arguments\": \"{\\\"email\\\":\\\"grace.kim@example.com\\\"}\"}}]}, {\"role\": \"tool\", \"tool_call_id\": \"call_TwwIWARdpqNmyYghfonMBRRP\", \"content\": \"{\\\"customer_id\\\": \\\"C-105\\\", \\\"name\\\": \\\"Grace Kim\\\", \\\"email\\\": \\\"grace.kim@example.com\\\", \\\"tier\\\": \\\"standard\\\"}\"}, {\"role\": \"assistant\", \"content\": null, \"tool_calls\": [{\"id\": \"call_AYbJIhEjdbPq1wMNQuBqgT47\", \"type\": \"function\", \"function\": {\"name\": \"transfer_to_orders\", \"arguments\": \"{\\\"note\\\":\\\"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\\\"}\"}}]}, {\"role\": \"tool\", \"tool_call_id\": \"call_AYbJIhEjdbPq1wMNQuBqgT47\", \"content\": \"{\\\"transferred_to\\\": \\\"orders\\\", \\\"note\\\": \\\"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it\\\\u2019s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\\\"}\"}]")
NOTE = "Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided."
SYSTEM = "You are the orders agent: order status, shipping and tracking, cancellations, address changes, confirmation emails."

# What the orders agent was really given, as its user message, in note mode and in log ("text") mode.
NOTE_USER = "Hi, grace.kim@example.com. My trekking poles never arrived and it's been weeks.\n\nHandoff note from the triage agent: Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided."
TEXT_USER = "Hi, grace.kim@example.com. My trekking poles never arrived and it's been weeks.\n\nWhat the team has done so far (you are taking over):\n[triage] called find_customer({\"email\":\"grace.kim@example.com\"})\n    → {\"customer_id\": \"C-105\", \"name\": \"Grace Kim\", \"email\": \"grace.kim@example.com\", \"tier\": \"standard\"}\n[triage] called transfer_to_orders({\"note\":\"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\"})\n    → {\"transferred_to\": \"orders\", \"note\": \"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it\\u2019s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\"}\n\nHandoff note from the triage agent: Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided."

# Every tool call in each run of T06 (trial 1), all agents in order.
NOTE_STEPS = json.loads("[{\"tool\": \"find_customer\", \"args\": {\"email\": \"grace.kim@example.com\"}, \"status\": \"ok\"}, {\"tool\": \"transfer_to_orders\", \"args\": {\"note\": \"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\"}, \"status\": \"handoff\"}, {\"tool\": \"find_customer\", \"args\": {\"email\": \"grace.kim@example.com\"}, \"status\": \"ok\"}, {\"tool\": \"list_orders\", \"args\": {\"customer_id\": \"C-105\"}, \"status\": \"ok\"}, {\"tool\": \"get_order\", \"args\": {\"order_id\": \"A-1010\"}, \"status\": \"ok\"}, {\"tool\": \"track_shipment\", \"args\": {\"tracking_id\": \"T-5010\"}, \"status\": \"ok\"}, {\"tool\": \"transfer_to_refunds\", \"args\": {\"note\": \"Customer C-105 (Grace Kim) reports trekking poles never arrived. Order A-1010 (placed 2026-09-30) item: Trekking Poles (pair), unit price $68.00. Tracking T-5010 shows status 'lost' (RoadRunner Freight); no delivered date/ETA. Please advise/refund or replacement options and process accordingly.\"}, \"status\": \"handoff\"}, {\"tool\": \"get_customer\", \"args\": {\"customer_id\": \"C-105\"}, \"status\": \"ok\"}, {\"tool\": \"get_order\", \"args\": {\"order_id\": \"A-1010\"}, \"status\": \"ok\"}, {\"tool\": \"track_shipment\", \"args\": {\"tracking_id\": \"T-5010\"}, \"status\": \"ok\"}, {\"tool\": \"get_refunds\", \"args\": {\"order_id\": \"A-1010\"}, \"status\": \"ok\"}, {\"tool\": \"issue_refund\", \"args\": {\"order_id\": \"A-1010\", \"sku\": \"POLE-2\", \"amount\": 68, \"reason\": \"Carrier marked shipment lost (RoadRunner Freight) — refund per policy for undelivered lost items.\"}, \"status\": \"ok\"}]")
TEXT_STEPS = json.loads("[{\"tool\": \"find_customer\", \"args\": {\"email\": \"grace.kim@example.com\"}, \"status\": \"ok\"}, {\"tool\": \"transfer_to_orders\", \"args\": {\"note\": \"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\"}, \"status\": \"handoff\"}, {\"tool\": \"find_customer\", \"args\": {\"email\": \"grace.kim@example.com\"}, \"status\": \"ok\"}, {\"tool\": \"list_orders\", \"args\": {\"customer_id\": \"C-105\"}, \"status\": \"ok\"}, {\"tool\": \"get_order\", \"args\": {\"order_id\": \"A-1010\"}, \"status\": \"ok\"}, {\"tool\": \"track_shipment\", \"args\": {\"tracking_id\": \"T-5010\"}, \"status\": \"ok\"}, {\"tool\": \"transfer_to_refunds\", \"args\": {\"note\": \"Customer Grace Kim (C-105) reports trekking poles never arrived. Order A-1010 (Trekking Poles (pair), $68.00) placed 2026-09-30; tracking T-5010 shows carrier status LOST (RoadRunner Freight), no delivery date/ETA. Please advise and process replacement and/or refund per policy; customer has not received items for weeks.\"}, \"status\": \"handoff\"}, {\"tool\": \"get_refunds\", \"args\": {\"order_id\": \"A-1010\"}, \"status\": \"ok\"}, {\"tool\": \"track_shipment\", \"args\": {\"tracking_id\": \"T-5010\"}, \"status\": \"ok\"}, {\"tool\": \"get_order\", \"args\": {\"order_id\": \"A-1010\"}, \"status\": \"ok\"}, {\"tool\": \"issue_refund\", \"args\": {\"order_id\": \"A-1010\", \"sku\": \"POLE-2\", \"amount\": 68, \"reason\": \"Carrier marked shipment as LOST (RoadRunner Freight); poles never arrived. Refund per policy for undelivered lost item.\"}, \"status\": \"ok\"}]")
FULL_STEPS = json.loads("[{\"tool\": \"find_customer\", \"args\": {\"email\": \"grace.kim@example.com\"}, \"status\": \"ok\"}, {\"tool\": \"transfer_to_orders\", \"args\": {\"note\": \"Customer Grace Kim (customer_id: C-105) says her trekking poles never arrived and it’s been weeks. Please check order status/shipping/tracking and assist with next steps (replacement/refund if appropriate). No order_id provided.\"}, \"status\": \"handoff\"}]")

# All 90 runs per format, in task order. "-": no handoff. After a handoff, "l"/"L": the last agent
# answered on its first call with no tool (passed/failed); "P"/"F": it used tools (passed/failed).
RUNS = {
    "note": "PPPPPPPPPPPPPPPPPPPPPFFFFFFPPPPPPFPPPPPFFFPPPPPPPPPPPPPPPPPP---PPPPPPPPPFFFPPPPPPPPPPPPFFF",
    "full": "PPPPLPPPPPPPPPLLLLPPPFFFFFFPPPPPPPPPPPPFFFPPPPPPPPPPPPPPPPPP---PPPPPPPPPFFFPPPPPPPPPFPPLFF",
    "text": "PPPPPPPPPPPPPPPPPPPPPFFFFFFPPPPPPPPPPPPFFFPPPPPPPPPPPPPPPPPP---PPPPPPPPPFFFPPPPPPPPPFPPFFF",
}


def decode(s):
    return [{"handoffs": 0 if c == "-" else 1, "path": ["triage"] if c == "-" else ["triage", "orders"],
             "last_turns": 1 if c in "lL" else 3, "success": c in "lP"} for c in s]


def test_the_log_says_who_did_what():
    log = render_log("triage", TRIAGE)
    assert len(log) == 4
    assert log[0] == '[triage] called find_customer({"email":"grace.kim@example.com"})'
    assert log[1].startswith('    → {"customer_id": "C-105"')
    assert log[2].startswith("[triage] called transfer_to_orders(")
    assert render_log("orders", [{"role": "assistant", "content": "Your refund is on its way.", "tool_calls": None}]) == [
        "[orders] replied to the customer: Your refund is on its way."]
    assert render_log("triage", [{"role": "user", "content": "hi"}]) == []


def test_note_mode_is_what_orders_got():
    msgs = next_input("note", SYSTEM, CUSTOMER, [], [], "triage", NOTE)
    assert [m["role"] for m in msgs] == ["system", "user"]
    assert msgs[0]["content"] == SYSTEM
    assert msgs[1]["content"] == NOTE_USER


def test_log_mode_is_what_orders_got():
    msgs = next_input("text", SYSTEM, CUSTOMER, [], render_log("triage", TRIAGE), "triage", NOTE)
    assert [m["role"] for m in msgs] == ["system", "user"]
    assert msgs[1]["content"] == TEXT_USER


def test_full_mode_hands_over_triages_messages_as_assistant():
    transcript = [{"role": "user", "content": CUSTOMER}] + TRIAGE
    msgs = next_input("full", SYSTEM, CUSTOMER, transcript, [], "triage", NOTE)
    # The orders agent's own system prompt, then triage's work marked "assistant": the role a model reads as itself.
    assert [m["role"] for m in msgs] == ["system", "user", "assistant", "tool", "assistant", "tool"]
    assert msgs[0]["content"] == SYSTEM
    assert msgs[4]["tool_calls"][0]["function"]["name"] == "transfer_to_orders"
    assert not any("triage" in json.dumps(m) for m in msgs[2:3])   # nothing in the lookup says whose it was


def test_repeated_lookups():
    # Note mode: orders looks Grace up again; refunds re-reads the order and the tracking.
    assert repeated_lookups(NOTE_STEPS) == 3
    assert repeated_lookups(TEXT_STEPS) == 3
    # Whole conversation: orders made no call at all, so nothing was repeated (and nothing was done).
    assert repeated_lookups(FULL_STEPS) == 0
    # Key order doesn't matter; failed calls and writes don't count.
    assert repeated_lookups([{"tool": "get_order", "args": {"a": 1, "b": 2}, "status": "ok"},
                             {"tool": "get_order", "args": {"b": 2, "a": 1}, "status": "ok"}]) == 1
    assert repeated_lookups([{"tool": "get_order", "args": {}, "status": "transient"},
                             {"tool": "get_order", "args": {}, "status": "ok"}]) == 0
    assert repeated_lookups([{"tool": "issue_refund", "args": {}, "status": "ok"}] * 2) == 0


def test_answered_at_once_after_a_handoff():
    assert answered_at_once(decode(RUNS["note"])) == (87, 0, 0)
    assert answered_at_once(decode(RUNS["full"])) == (87, 6, 6)    # all six failed
    assert answered_at_once(decode(RUNS["text"])) == (87, 0, 0)


def test_ping_pong():
    assert ping_pong(["triage", "orders", "refunds"]) == 0
    assert ping_pong(["triage", "orders", "refunds", "orders"]) == 1
    assert ping_pong(["a", "b", "a", "b"]) == 2
    assert ping_pong(["triage"]) == 0
    # The team's routes over 270 runs: one bounced back.
    routes = {("triage", "orders"): 133, ("triage", "refunds"): 90, ("triage", "orders", "refunds"): 19,
              ("triage", "catalog"): 18, ("triage",): 9, ("triage", "orders", "refunds", "orders"): 1}
    assert sum(routes.values()) == 270
    assert sum(n for path, n in routes.items() if ping_pong(list(path))) == 1
