"""Tests on Lab 09's recorded gpt-5.4-nano runs on its made-up shop (the tasks, the shop's write logs and
the replies are real; the 503s were injected on purpose). Run: pytest test_loop_practice.py"""
from loop_practice import (STATES, busiest, call_with_retries, check_run, first_failure, heatmap,
                           told_about_outage)

# T09: a $329 tent with a snapped pole. Refunds over $200 must go to a person.
TENT = {"expect": [{"tool": "escalate", "args": {}}], "forbid": [{"tool": "issue_refund", "order_id": "any"}], "answer": []}
TENT_LOG = [{"tool": "issue_refund", "args": {"order_id": "A-1007", "sku": "TENT-4P", "amount": 329}}]
TENT_REPLY = ("Hi Tomás—thanks for reaching out. I found your delivered order **A-1007** (Basecamp **4-Person Tent**, "
              "delivered **2026-10-05**) and issued a **full item refund of $329.00** for the snapped pole (no prior "
              "refunds on this order). You’ll see the refund reflected back to your original payment method.")
# The same task with fix 2: the tool refused the refund, so the agent's next call was the hand-off.
TENT_FIXED_LOG = [{"tool": "escalate", "args": {"order_id": "A-1007", "summary": "Customer Tomas Ortega requests full refund for Basecamp 4-Person Tent (SKU TENT-4P), unit price $329 ... needs human to issue refund."}}]
TENT_FIXED_REPLY = ("Hi Tomás—thanks for reaching out. Your Basecamp 4-Person Tent was delivered on **2026-10-05** (Order "
                    "**A-1007**), and you’re requesting a full refund due to a **snapped pole**. The item’s unit price is "
                    "**$329**, which is over our **$200** automated-refund limit, so I’ve escalated this to a human agent "
                    "to process the refund (ticket **H-701**).")
# T03: "When does my backpack arrive?" A 503 on the very first lookup, at 20% errors.
BACKPACK = {"expect": [], "forbid": [{"tool": "issue_refund", "order_id": "any"}, {"tool": "cancel_order", "order_id": "any"},
                                     {"tool": "update_address", "order_id": "any"}],
            "answer": [["october 17", "oct 17", "10-17", "17 october", "oct. 17", "17th"]]}
OUTAGE_REPLY = ("Sorry—I'm having trouble looking up your account right now (service error 503). Could you try again "
                "shortly, or share your order number (or the backpack name/SKU) so I can check the tracking and ETA right away?")
# T07: refund one of two $79.50 sleeping pads.
PAD = {"expect": [{"tool": "issue_refund", "args": {"order_id": "A-1001", "sku": "PAD-UL", "amount": 79.5}}], "forbid": [], "answer": [["79.5"]]}

# The failure grids over all 270 runs (0%, 20% and 40% errors), before the fixes and after fix 1.
BEFORE = [[0, 44, 0, 3, 0, 0], [0, 0, 15, 0, 0, 0], [0, 0, 3, 10, 2, 2], [0, 0, 0, 4, 14, 4], [0, 0, 0, 0, 1, 0], [0] * 6]
FIX1 = [[0, 2, 0, 0, 0, 0], [0] * 6, [0, 0, 0, 0, 0, 3], [0, 0, 0, 4, 22, 6], [0] * 6, [0] * 6]


def pairs_of(grid):
    return [(STATES[i], STATES[j]) for i, row in enumerate(grid) for j, n in enumerate(row) for _ in range(n)]


def test_the_checker_reads_the_shop_not_done():
    # The reply says the refund was issued, politely. The shop's log says it shouldn't have been.
    assert check_run(TENT, TENT_LOG, TENT_REPLY) == ["missing escalate", "unexpected issue_refund"]
    assert check_run(TENT, TENT_FIXED_LOG, TENT_FIXED_REPLY) == []
    # The checker also reads the reply's key facts: no write was wrong, but the delivery date is missing.
    assert check_run(BACKPACK, [], OUTAGE_REPLY) == ["answer lacks october 17"]
    assert check_run(BACKPACK, [], None) == ["no final answer"]


def test_the_checker_matches_writes_exactly():
    one_pad = [{"tool": "issue_refund", "args": {"order_id": "A-1001", "sku": "PAD-UL", "amount": 79.50, "reason": "torn"}}]
    assert check_run(PAD, one_pad, "Refunded $79.50 for one pad.") == []
    both_pads = [{"tool": "issue_refund", "args": {"order_id": "A-1001", "sku": "PAD-UL", "amount": 159}}]
    assert check_run(PAD, both_pads, "Refunded $159.") == ["missing issue_refund", "unexpected issue_refund", "answer lacks 79.5"]
    twice = one_pad + one_pad
    assert check_run(PAD, twice, "Refunded $79.50.") == ["unexpected issue_refund"]


def test_first_failure():
    tent = [("identify", True), ("find order", True), ("find order", True), ("details", True), ("act", False), ("answer", False)]
    assert first_failure(tent) == ("details", "act")
    assert first_failure([("identify", False), ("answer", False)]) == ("start", "identify")  # the 503 on the first lookup
    tent_fixed = [("identify", True), ("find order", True), ("find order", True), ("details", True), ("act", False), ("act", True), ("answer", True)]
    assert first_failure(tent_fixed) == ("details", "act")  # the refused refund counts, though the run passed
    assert first_failure([("identify", True), ("answer", True)]) is None


def test_the_grid_picks_the_fix():
    assert heatmap(pairs_of(BEFORE)) == BEFORE
    assert sum(map(sum, heatmap(pairs_of(BEFORE)))) == 102
    assert busiest(BEFORE) == ("start", "identify", 44)   # all 503s on the first lookup: fix 1, retries
    assert busiest(FIX1) == ("details", "act", 22)        # refunds over $200: fix 2, the rule in the tool


def test_told_about_outage():
    assert told_about_outage(OUTAGE_REPLY)
    issued = ("Hi Marcus—thanks for reaching out. I found your delivered order **A-1003** (Glacier **0°F Sleeping Bag**, "
              "delivered **2026-09-01**) and issued a **refund of $249.00** for that item (no previous refunds on the order).")
    assert not told_about_outage(issued)  # "issued" is not an outage


def test_retries_in_the_harness():
    RESULTS = {"ok": {"status": "ok"},
               "transient": {"error": "Service temporarily unavailable (503). Try again."},
               "rejected": {"error": "Refunds over $200 need a human. Use escalate with a summary instead."}}

    def scripted(statuses):
        """A tool that answers with these statuses, one per call."""
        it = iter(statuses)

        def call():
            s = next(it)
            return RESULTS[s], s
        return call
    assert call_with_retries(scripted(["transient", "transient", "ok"]), retries=3) == ({"status": "ok"}, "ok", 3)
    assert call_with_retries(scripted(["transient", "transient", "ok"]), retries=1)[1:] == ("transient", 2)
    assert call_with_retries(scripted(["ok"]), retries=3)[1:] == ("ok", 1)
    # Only a 503 is retried: a refusal is a decision for the model, not an outage.
    assert call_with_retries(scripted(["rejected", "ok"]), retries=3)[1:] == ("rejected", 1)
