"""Recette relationnelle fictive. Aucun Elasticsearch, stock ou paiement réel."""
import copy
import itertools
import sqlite3

# produit, offre, prix EUR/USD en centimes, devise, quantité, délai, active
ROWS = [
    ('P1','A',8000,'EUR',0,1,1), ('P1','B',12000,'EUR',5,2,1),
    ('P2','C',9500,'EUR',3,2,1), ('P2','D',6000,'EUR',4,5,1),
    ('P3','E',9000,'EUR',2,1,1), ('P3','F',9900,'EUR',1,2,1),
    ('P4','G',7000,'EUR',10,1,0), ('P4','H',8500,'USD',2,1,1),
]
PREDICATE = "active=1 AND currency='EUR' AND price<=10000 AND qty>=1 AND days<=2"

def database(rows):
    db = sqlite3.connect(':memory:')
    db.execute('CREATE TABLE offers(product TEXT, offer TEXT PRIMARY KEY, price INTEGER, currency TEXT, qty INTEGER, days INTEGER, active INTEGER)')
    db.executemany('INSERT INTO offers VALUES (?,?,?,?,?,?,?)', rows)
    return db

def eligible(db):
    return db.execute('SELECT product,offer,price FROM offers WHERE '+PREDICATE+' ORDER BY product,price,offer').fetchall()

def cards(db):
    return db.execute('WITH eligible AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY product ORDER BY price,offer) AS rank FROM offers WHERE '+PREDICATE+') SELECT product,offer,price FROM eligible WHERE rank=1 ORDER BY price,product').fetchall()

def independent_filters(db):
    # Mauvais témoin : chaque critère peut être satisfait par une offre différente.
    predicates = ['active=1',"currency='EUR'",'price<=10000','qty>=1','days<=2']
    return db.execute('SELECT DISTINCT p.product FROM offers p WHERE '+' AND '.join('EXISTS(SELECT 1 FROM offers WHERE product=p.product AND '+p+')' for p in predicates)+' ORDER BY p.product').fetchall()

def main():
    checks=0
    def check(condition):
        nonlocal checks
        if not condition:
            raise AssertionError('échec du contrôle '+str(checks+1))
        checks+=1
    db=database(ROWS)
    expected=[('P3','E',9000),('P2','C',9500)]
    check(eligible(db)==[('P2','C',9500),('P3','E',9000),('P3','F',9900)])
    check(cards(db)==expected)
    check(independent_filters(db)==[('P1',),('P2',),('P3',),('P4',)])
    # Deuxième témoin : bon ensemble de produits, mauvais prix et mauvais ordre.
    check(db.execute("SELECT product,MIN(price) FROM offers WHERE product IN ('P2','P3') GROUP BY product ORDER BY MIN(price)").fetchall()==[('P2',6000),('P3',9000)])
    # Troisième témoin : trois offres ne font pas trois produits.
    check(len(eligible(db))==3 and len(cards(db))==2)
    # Quatrième témoin : retirer un produit dès qu'une offre est non admissible.
    check(db.execute('SELECT DISTINCT p.product FROM offers p WHERE NOT EXISTS(SELECT 1 FROM offers WHERE product=p.product AND NOT ('+PREDICATE+')) ORDER BY p.product').fetchall()==[('P3',)])
    for product_order in itertools.permutations(['P1','P2','P3','P4']):
        rows=[r for product in product_order for r in reversed(ROWS) if r[0]==product]
        with database(rows) as permuted:
            check(cards(permuted)==expected)
    # Bornes : prix inclusif, quantité, délai, statut, devise et absence.
    for price,currency,qty,days,active,accepted in [
        (10000,'EUR',1,2,1,True), (10001,'EUR',1,2,1,False),
        (9999,'EUR',0,2,1,False), (9999,'EUR',1,3,1,False),
        (9999,'EUR',1,2,0,False), (9999,'USD',1,2,1,False),
        (9999,'EUR',1,None,1,False), (None,'EUR',1,2,1,False),
    ]:
        with database([('PX','X',price,currency,qty,days,active)]) as boundary:
            check(bool(cards(boundary))==accepted)
    # L'absence totale d'offre ne peut créer une carte.
    with database([]) as empty:
        check(cards(empty)==[])
    # Une égalité de prix doit avoir un choix stable, pas dépendre de l'ordre.
    tied=copy.deepcopy(ROWS)
    tied=[(p,o,9000 if o=='F' else v,c,q,d,a) for p,o,v,c,q,d,a in tied]
    with database(tied) as equality:
        check(cards(equality)==expected)
    # Une photographie de recherche peut être cohérente et déjà périmée.
    db.execute("UPDATE offers SET qty=0 WHERE offer='C'")
    check(cards(db)==[('P3','E',9000)])
    check(db.execute('SELECT offer FROM offers WHERE offer=? AND '+PREDICATE,('C',)).fetchall()==[])
    print(f'{checks} contrôles réussis ; 24 permutations ; 4 témoins insuffisants détectés.')
    print('Résultat initial : P3 / E / 90 EUR puis P2 / C / 95 EUR ; 3 offres, 2 produits.')
    print('Après épuisement de C : P3 seul. Aucun Elasticsearch ni achat exécuté.')

if __name__=='__main__':
    main()
