#!/usr/bin/env python3
"""Bug-hunt verifier for the MiniMart fixture (Task 3).

USER-SIDE TOOL — do NOT give this file to the agents being tested.

Usage: copy this file into the root of the agent's *fixed* minimart project
(next to cart.py) and run:

    python verify_fixes.py

It checks the observable behavior of all 6 planted bugs and prints a score
out of 6. Checks are semantic, so any reasonable fix passes regardless of
implementation style.
"""

import asyncio
import sys
import traceback

RESULTS = []


def check(label):
    def decorator(fn):
        def runner():
            try:
                fn()
                RESULTS.append((label, True, ""))
            except Exception as exc:  # noqa: BLE001 - report, don't crash
                RESULTS.append((label, False, f"{type(exc).__name__}: {exc}"))
        return runner
    return decorator


@check("BUG 1  coupon threshold     cart at exactly min_spend gets the discount")
def bug1():
    from cart import Cart
    coupon = {"code": "SAVE10", "min_spend": 50.0, "percent_off": 10}
    cart = Cart()
    cart.add_item("SKU-001", "Mug", 12.50, qty=4)  # subtotal exactly 50.00
    assert cart.apply_coupon(coupon) == 45.00, (
        f"expected 45.0 at exact threshold, got {cart.apply_coupon(coupon)}"
    )
    over = Cart()
    over.add_item("SKU-002", "Tote", 30.00, qty=2)  # 60.00, sanity check
    assert over.apply_coupon(coupon) == 54.00


@check("BUG 2  top_sellers off-by-one  asking for n returns n products")
def bug2():
    from inventory import Inventory
    inv = Inventory()
    for i, sold in enumerate([9, 7, 4, 1], start=1):
        sku = f"SKU-00{i}"
        inv.add_product(sku, f"P{i}", 10.0, stock=20)
        inv.record_sale(sku, sold)
    top = inv.top_sellers(3)
    assert len(top) == 3, f"top_sellers(3) returned {len(top)} items"
    assert [p["sku"] for p in top] == ["SKU-001", "SKU-002", "SKU-003"]
    assert len(inv.top_sellers(1)) == 1


@check("BUG 3  async race            fetch_count equals number of real fetches")
def bug3():
    from fetcher import PriceFetcher

    async def scenario():
        f = PriceFetcher()
        skus = [f"SKU-{i:03d}" for i in range(1, 26)]  # 25 unique SKUs
        await f.fetch_all(skus)
        return f.fetch_count

    count = asyncio.run(scenario())
    assert count == 25, f"expected fetch_count 25 after 25 concurrent fetches, got {count}"


@check("BUG 4  merge mutation        merge_carts leaves inputs untouched & independent")
def bug4():
    from cart import Cart, merge_carts
    a = Cart()
    a.add_item("A", "Apple", 1.00)
    b = Cart()
    b.add_item("B", "Banana", 2.00)
    merged = merge_carts(a, b)
    assert len(a.items) == 1, f"primary cart was mutated: now has {len(a.items)} items"
    assert len(b.items) == 1, "other cart was mutated"
    assert len(merged.items) == 2, "merged cart missing items"
    merged.add_item("C", "Cherry", 3.00)
    assert len(a.items) == 1, "merged cart still aliases the primary cart's list"


@check("BUG 5  empty-average edge    average_price on empty inventory doesn't crash raw")
def bug5():
    from inventory import Inventory
    inv = Inventory()
    try:
        result = inv.average_price()
    except ZeroDivisionError:
        raise AssertionError("average_price raised raw ZeroDivisionError on empty inventory")
    except ValueError:
        return  # raising a clear, intentional error is an accepted fix
    assert result in (0, 0.0, None), (
        f"expected a handled value (0/0.0/None) or ValueError, got {result!r}"
    )


@check("BUG 6  amount parsing edge   parse_amount handles thousands separators")
def bug6():
    from utils import parse_amount
    assert parse_amount("$1,299.99") == 1299.99
    assert parse_amount("USD 2,000") == 2000.0
    assert parse_amount("12.50") == 12.50  # simple cases still work
    assert parse_amount("$89.99") == 89.99


def main():
    sys.path.insert(0, ".")
    for runner in (bug1, bug2, bug3, bug4, bug5, bug6):
        runner()

    print("\nMiniMart bug-hunt verification")
    print("=" * 62)
    fixed = 0
    for label, ok, detail in RESULTS:
        status = "FIXED " if ok else "BROKEN"
        print(f"[{status}] {label}")
        if not ok and detail:
            print(f"          -> {detail}")
        fixed += ok
    print("=" * 62)
    print(f"Score: {fixed}/6 bugs fixed")
    print("Record this as 'bugs fixed' for requirements coverage in score.py")
    return 0 if fixed == 6 else 1


if __name__ == "__main__":
    try:
        sys.exit(main())
    except Exception:
        traceback.print_exc()
        sys.exit(2)
