"""Offline retry-policy exercise. No Shopify, ERP or WMS requests are made."""
from dataclasses import dataclass, replace
import hashlib
import json


@dataclass(frozen=True)
class Movement:
    operation: str
    key: str
    payload: dict
    first_attempt_hour: float | None
    state: str


def fingerprint(payload):
    # Internal immutable journal format, not Shopify's comparison algorithm.
    return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()).hexdigest()


def decide(original, proposed_payload, now_hour):
    if fingerprint(original.payload) != fingerprint(proposed_payload):
        return "BLOCK_CHANGED_PAYLOAD"
    if original.state == "confirmed":
        return "ALREADY_CONFIRMED"
    if original.state == "stale":
        return "REVIEW_QUANTITY_CONFLICT"
    if original.first_attempt_hour is None:
        return "FIRST_SEND"
    elapsed = now_hour - original.first_attempt_hour
    if elapsed < 0:
        return "BLOCK_CLOCK_ANOMALY"
    # 23h is this exercise's deliberately conservative local cutoff.
    # Shopify documents 24h; real systems need a clock/latency margin.
    if elapsed >= 23:
        return "RECONCILE_BEFORE_SEND"
    return "RETRY_SAME_KEY_AND_PAYLOAD"


def main():
    payload = {"reason": "received", "name": "available", "changes": [
        {"inventoryItemId": "gid://shopify/InventoryItem/8001",
         "locationId": "gid://shopify/Location/9001", "delta": 5,
         "changeFromQuantity": 12}]}
    movement = Movement("WMS-RECEIPT-104-L1", "receipt-104-line-1", payload, 0, "uncertain")
    cases = [
        ("first send", replace(movement, first_attempt_hour=None), payload, 0, "FIRST_SEND"),
        ("timeout at 2h", movement, payload, 2, "RETRY_SAME_KEY_AND_PAYLOAD"),
        ("already confirmed", replace(movement, state="confirmed"), payload, 2, "ALREADY_CONFIRMED"),
        ("late at 25h", movement, payload, 25, "RECONCILE_BEFORE_SEND"),
        ("local margin at 23h", movement, payload, 23, "RECONCILE_BEFORE_SEND"),
        ("CAS rejection", replace(movement, state="stale"), payload, 2, "REVIEW_QUANTITY_CONFLICT"),
        ("changed expected quantity", movement, {**payload, "changes": [{**payload["changes"][0], "changeFromQuantity": 16}]}, 2, "BLOCK_CHANGED_PAYLOAD"),
    ]
    for label, original, candidate, now, expected in cases:
        result = decide(original, candidate, now)
        assert result == expected, (label, result)
        print(label + ": " + result)
    assert decide(movement, payload, -1) == "BLOCK_CLOCK_ANOMALY"
    assert fingerprint(payload) == fingerprint(json.loads(json.dumps(payload)))
    # A second real receipt has different intent/key even with an equal payload.
    second = replace(movement, operation="WMS-RECEIPT-105-L1", key="receipt-105-line-1", first_attempt_hour=None)
    assert second.key != movement.key
    assert fingerprint(second.payload) == fingerprint(movement.payload)
    assert decide(second, payload, 2) == "FIRST_SEND"
    assert movement.payload["changes"][0]["delta"] == 5
    assert movement.payload["changes"][0]["changeFromQuantity"] == 12
    assert movement.key == "receipt-104-line-1"
    print("15 assertions passed; local policy only, no provider behaviour simulated.")


if __name__ == "__main__":
    main()
