"""Modèle pédagogique local, sans appel Shopify ni écriture ERP.

Python >= 3.10. Les fractions représentent un état fictif connu du modèle ;
elles ne sont pas déduites des réponses de Shopify. Le curseur entier de ce
modèle n'imite pas le curseur opaque de l'API. Aucun stockage durable n'est
implémenté : les assertions explicitent le contrat à vérifier en recette.
"""
from datetime import datetime, timedelta, timezone


def instant(value: str) -> datetime:
    return datetime.fromisoformat(value.replace("Z", "+00:00"))


LOW = instant("2026-10-23T12:00:02Z")
HIGH = instant("2026-10-23T12:01:00Z")
ORDERS = [
    ("A", instant("2026-10-23T12:00:02.100Z")),
    ("B", instant("2026-10-23T12:00:02.800Z")),
    ("C", instant("2026-10-23T12:00:03.100Z")),
    ("D", HIGH),
]


def window(inclusive: bool) -> list[str]:
    # Modélisation de > sans fraction en 2026-10 : seconde entière exclue.
    start = LOW if inclusive else LOW + timedelta(seconds=1)
    return [identifier for identifier, timestamp in ORDERS
            if start <= timestamp < HIGH]


def persist(received: set[tuple[str, str]], shop: str, page: list[str]) -> int:
    before = len(received)
    received.update((shop, identifier) for identifier in page)
    return len(received) - before


def main() -> None:
    assert window(False) == ["C"]
    assert window(True) == ["A", "B", "C"]
    print("Borne exclusive :", window(False))
    print("Borne inclusive :", window(True))

    received = {("boutique-nord", "A")}
    assert persist(received, "boutique-nord", window(True)) == 2
    assert persist(received, "boutique-nord", window(True)) == 0
    assert persist(received, "boutique-sud", ["A"]) == 1

    pages = [["A", "B"], ["C"]]
    after_crash: set[tuple[str, str]] = set()
    persist(after_crash, "boutique-nord", pages[0])
    completed = False  # Panne avant la deuxième page, fenêtre non clôturée.
    assert not completed
    assert after_crash == {("boutique-nord", "A"), ("boutique-nord", "B")}
    print("Après panne :", sorted(after_crash), "fenêtre terminée :", completed)
    for page in pages:  # Redémarrage conservateur depuis le début.
        persist(after_crash, "boutique-nord", page)
    completed = True
    assert after_crash == {( "boutique-nord", item) for item in ("A", "B", "C")}
    assert completed
    print("Après reprise :", sorted(after_crash), "fenêtre terminée :", completed)

    next_window = [identifier for identifier, timestamp in ORDERS
                   if HIGH <= timestamp < HIGH + timedelta(minutes=1)]
    assert next_window == ["D"]
    assert "D" not in window(True)

    # Contre-exemple : curseur avancé avant la conservation de la page 0.
    unsafe_received: set[tuple[str, str]] = set()
    unsafe_next_page = 1
    for page in pages[unsafe_next_page:]:
        persist(unsafe_received, "boutique-nord", page)
    missing = after_crash - unsafe_received
    assert missing == {("boutique-nord", "A"), ("boutique-nord", "B")}
    print("Curseur avancé trop tôt, commandes perdues :", sorted(missing))
    print("12 assertions réussies ; modèle local uniquement, aucune API appelée.")


if __name__ == "__main__":
    main()
