"""Contrat pédagogique local : ni navigateur, ni paiement, ni API réelle."""
from copy import deepcopy

OLD = dict(sku='FILTRE-20', revision=17, price_cents=8000, stock=6)
NEW = dict(sku='FILTRE-20', revision=18, price_cents=9000, stock=3)

class View:
    def __init__(self):
        self.sku = 'FILTRE-20'
        self.quantity = 2
        self.generation = 0
        self.quote = deepcopy(OLD)
        self.state = 'ready'
        self.accepted_revision = 17

    def refresh(self):
        self.generation += 1
        self.state = 'checking'
        self.quote = None
        self.accepted_revision = None
        return self.generation

    def receive(self, generation, response):
        if generation != self.generation:
            return 'ignored'
        required = {'sku', 'revision', 'price_cents', 'stock'}
        if (not isinstance(response, dict) or set(response) != required
            or response['sku'] != self.sku
            or any(type(response[k]) is not int for k in ('revision', 'price_cents', 'stock'))
            or response['revision'] < 1 or response['price_cents'] < 0 or response['stock'] < 0):
            self.state, self.quote = 'invalid', None
            return 'invalid'
        self.quote = deepcopy(response)
        self.state = 'review' if response['stock'] >= self.quantity else 'unavailable'
        return self.state

    def fail(self, generation):
        if generation == self.generation:
            self.generation += 1
            self.state, self.quote, self.accepted_revision = 'failed', None, None

    def approve(self):
        if self.state != 'review':
            raise ValueError('Une proposition vérifiée doit être affichée.')
        self.accepted_revision = self.quote['revision']
        self.state = 'ready'

    def payload(self):
        if self.state != 'ready' or self.quote is None:
            raise ValueError('Achat indisponible tant que la vérification est incomplète.')
        return dict(sku=self.sku, quantity=self.quantity,
                    revision=self.accepted_revision,
                    total_cents=self.quote['price_cents'] * self.quantity)


def server_accepts(payload, current):
    """Vérification pure, pas de réservation ni de débit. Atomicité non simulée."""
    return (payload['sku'] == current['sku']
            and type(payload['quantity']) is int and payload['quantity'] > 0
            and payload['quantity'] <= current['stock']
            and payload['revision'] == current['revision']
            and payload['total_cents'] == current['price_cents'] * payload['quantity'])


def run():
    checks = 0
    def check(condition):
        nonlocal checks
        assert condition
        checks += 1
    def blocked(view):
        try:
            view.payload()
        except ValueError:
            return True
        return False

    v = View()
    stale = v.payload()
    check(stale['total_cents'] == 16000)  # témoin1 : restaurer n'actualise pas
    check(not server_accepts(stale, NEW))
    first = v.refresh()
    check(v.state == 'checking' and v.quote is None and blocked(v))
    check(v.receive(first, NEW) == 'review')
    check(v.quote['price_cents'] * v.quantity == 18000)
    check(blocked(v))  # afficher180 ne vaut pas accord
    v.approve()
    check(server_accepts(v.payload(), NEW))
    check(v.payload()['total_cents'] - stale['total_cents'] == 2000)

    a, b = v.refresh(), v.refresh()
    check(v.receive(b, NEW) == 'review')
    check(v.receive(a, OLD) == 'ignored')
    check(v.quote == NEW)  # témoin2 : dernier arrivé n'est pas dernier demandé
    v.fail(a)
    check(v.state == 'review')
    v.fail(b)
    check(v.state == 'failed' and blocked(v))
    check(v.receive(b, NEW) == 'ignored')
    check(v.state == 'failed' and blocked(v))

    for bad in [dict(NEW, sku='AUTRE'), dict(NEW, stock=-1), dict(NEW, stock=True),
                dict(NEW, price_cents=90.5), dict(NEW, revision=0), {}, None]:
        g = v.refresh()
        check(v.receive(g, bad) == 'invalid' and blocked(v))
    g = v.refresh()
    check(v.receive(g, dict(NEW, stock=0)) == 'unavailable' and blocked(v))
    g = v.refresh()
    check(v.receive(g, dict(NEW, stock=1)) == 'unavailable' and blocked(v))
    g = v.refresh()
    check(v.receive(g, NEW) == 'review')
    v.approve()
    payload = v.payload()
    check(not server_accepts(payload, dict(NEW, revision=19, price_cents=9500)))
    check(not server_accepts(payload, dict(NEW, stock=1)))
    check(not server_accepts(dict(payload, total_cents=16000), NEW))
    check(not server_accepts(dict(payload, quantity=0), NEW))
    check(not server_accepts(dict(payload, sku='AUTRE'), NEW))
    # témoin3 : l'ancienne lecture peut encore autoriser180 quand la source exige190.
    check(server_accepts(payload, NEW) and not server_accepts(payload, dict(NEW, revision=19, price_cents=9500)))
    print(f'{checks} contrôles réussis ; 3 témoins insuffisants détectés ; aucun navigateur ni paiement exécuté.')

if __name__ == '__main__':
    run()
