"""Exercice fictif eBay : aucune API, commande ou modification de stock distante.

Les deux quantités sont explicitement renseignées. La somme des allocations
est une politique interne choisie, pas une modélisation du panier eBay.
"""
from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Product:
    seller: str
    sku: str
    quantity: int
    revision: int

@dataclass(frozen=True)
class Offer:
    seller: str
    sku: str
    offer_id: str
    site: str
    quantity: int | None
    published: bool = True

SITES = {'O-FR': 'EBAY_FR', 'O-DE': 'EBAY_DE'}

def valid_quantity(q):
    return type(q) is int and q >= 0

def known_offer(product, offer):
    return (offer.seller == product.seller and offer.sku == product.sku
            and SITES.get(offer.offer_id) == offer.site
            and valid_quantity(offer.quantity) and offer.published)

def listing_quantity(product, offer):
    if not valid_quantity(product.quantity) or not known_offer(product, offer):
        raise ValueError('Périmètre ou quantité non qualifié')
    return min(product.quantity, offer.quantity)

def acceptable_plan(product, targets, current_revision):
    if product.revision != current_revision or not valid_quantity(product.quantity):
        return False
    if len(targets) != len(SITES) or {o.offer_id for o in targets} != set(SITES):
        return False
    return (all(known_offer(product, o) for o in targets)
            and sum(o.quantity for o in targets) <= product.quantity)

def can_close(product, targets, readback, results, current_revision):
    # Les observations sont supposées collectées après la dernière écriture et
    # qualifiées complètes par l'appelant ; ce programme ne les collecte pas.
    if not acceptable_plan(product, targets, current_revision):
        return False
    if len(readback) != len(targets) or {o.offer_id for o in readback} != set(SITES):
        return False
    if set(results) != set(SITES) or not all(results.values()):
        return False
    by_id = {o.offer_id: o for o in readback}
    return all(known_offer(product, by_id[o.offer_id])
               and by_id[o.offer_id].quantity == o.quantity
               and listing_quantity(product, by_id[o.offer_id]) == o.quantity
               for o in targets)

checks = 0
def check(condition):
    global checks
    assert condition
    checks += 1

p = Product('VENDEUR-A', 'POMPE-12', 12 - 5 - 1, 17)
fr = Offer(p.seller, p.sku, 'O-FR', 'EBAY_FR', 0)
de = Offer(p.seller, p.sku, 'O-DE', 'EBAY_DE', 4)
targets = [replace(fr, quantity=2), de]
results = {'O-FR': True, 'O-DE': True}
check(p.quantity == 6)
check(listing_quantity(p, fr) == 0)
check(listing_quantity(p, de) == 4)
check(listing_quantity(replace(p, quantity=12), fr) == 0)
check(acceptable_plan(p, targets, 17))
check([listing_quantity(p, o) for o in targets] == [2, 4])
check(can_close(p, targets, targets, results, 17))
check(not can_close(p, targets, [fr, de], results, 17))
check(not can_close(p, targets, targets, {'O-FR': False, 'O-DE': True}, 17))
check(not can_close(p, targets, targets, {'O-DE': True}, 17))
check(not can_close(p, targets, [de], results, 17))
check(not can_close(p, targets, [targets[0], targets[0]], results, 17))
check(not can_close(p, targets, [replace(targets[0], quantity=None), de], results, 17))
check(not acceptable_plan(p, [replace(fr, quantity=6), replace(de, quantity=6)], 17))
check(not acceptable_plan(p, [replace(fr, quantity=2, seller='VENDEUR-B'), de], 17))
check(not acceptable_plan(p, [replace(fr, quantity=2, sku='POMPE-13'), de], 17))
check(not acceptable_plan(p, [replace(fr, quantity=2, site='EBAY_DE'), de], 17))
check(not acceptable_plan(p, [replace(fr, quantity=2, published=False), de], 17))
check(not acceptable_plan(p, [replace(fr, quantity=-1), de], 17))
check(not acceptable_plan(p, [replace(fr, quantity=True), de], 17))
check(not acceptable_plan(p, targets, 18))
new = replace(p, quantity=4, revision=18)
check(not acceptable_plan(new, targets, 18))
new_targets = [targets[0], replace(de, quantity=2)]
check(acceptable_plan(new, new_targets, 18))
check(can_close(new, new_targets, new_targets, results, 18))
check([listing_quantity(new, o) for o in new_targets] == [2, 2])
# Trois témoins incorrects : stock produit seul, appel seul, absence convertie en0.
bad_product_only = p.quantity > 0
bad_transport_only = 200 <= 200 < 300
bad_missing_is_zero = (None or 0) == 0
check(bad_product_only and not can_close(p, targets, [fr, de], results, 17))
check(bad_transport_only and not can_close(p, targets, targets, {'O-FR': False, 'O-DE': True}, 17))
check(bad_missing_is_zero and not can_close(p, targets, [replace(targets[0], quantity=None), de], results, 17))
print(f'{checks} contrôles réussis ; 3 clôtures incorrectes détectées')
print('Stock disponible6 ; avant FR0/DE4 ; cible FR2/DE4 ; après mouvement stock4 : nouvelle cible FR2/DE2.')
print('Données fictives, aucune API eBay, réservation concurrente ni commande exécutée.')
