from flask import Flask, request, jsonify
import re
import unicodedata
from functools import lru_cache
import time

app = Flask(__name__)


import json
import os
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from pytz import timezone

LABOS_JSON_URL = "https://vence-pharmacieposte.fr/modules/ajaxsmartsearch/export_labos.php"

# Déclenche la génération du fichier labos.json côté PrestaShop
def update_labos_json():
    try:
        resp = requests.get(LABOS_JSON_URL, timeout=30)
        if resp.status_code == 200:
            print(f"[NLP DEBUG] Mise à jour labos.json côté PrestaShop : OK ({resp.json()['count']} labos)")
        else:
            print(f"[NLP DEBUG] Erreur lors de la mise à jour labos.json : {resp.status_code}")
    except Exception as e:
        print(f"[NLP DEBUG] Exception lors de la mise à jour labos.json : {e}")

def schedule_labos_update():
    scheduler = BackgroundScheduler(timezone=timezone('Europe/Paris'))
    # Tous les jours à minuit heure française
    scheduler.add_job(update_labos_json, 'cron', hour=0, minute=0)
    scheduler.start()

# Appelle cette fonction au démarrage du service Python
schedule_labos_update()

def load_labos():
    # Chemin à adapter selon l'endroit où PrestaShop génère le fichier
    path = os.path.join(os.path.dirname(__file__), 'labos.json')
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return [l.lower() for l in json.load(f)]
    except Exception as e:
        print(f"[NLP DEBUG] Impossible de charger la liste des labos: {e}")
        return []

def normalize(text):
    # Supprime les accents et met en minuscule
    return ''.join(
        c for c in unicodedata.normalize('NFD', text)
        if unicodedata.category(c) != 'Mn'
    ).lower()

# Décorer la fonction parse_query avec un cache en mémoire
@lru_cache(maxsize=1000)
def parse_query(query):
    # Optimisation : normalisation préalable de la requête
    query = query.lower().strip()
    if not query or len(query) < 3:
        return None
        
    price_min, price_max, labo = None, None, None
    
    # Chargement optimisé des laboratoires avec cache
    labos_dyn = load_labos()
    
    # Création du mapping normalisé -> labo original (avec cache)
    static_normalized_labos = getattr(parse_query, '_normalized_labos', None)
    if static_normalized_labos is None or len(static_normalized_labos) != len(labos_dyn):
        normalized_labos = {normalize(l): l for l in labos_dyn}
        parse_query._normalized_labos = normalized_labos
    else:
        normalized_labos = static_normalized_labos
    
    # Analyse optimisée des tokens
    tokens = query.split()
    labo = None
    name_tokens = []
    
    # Utilisation d'un set pour la recherche de laboratoires (plus rapide)
    normalized_tokens = {normalize(token) for token in tokens}
    
    # Recherche rapide de correspondance de laboratoire
    for n_token in normalized_tokens:
        if n_token in normalized_labos:
            labo = normalized_labos[n_token]
            break
    
    # Construction des tokens de nom (seulement si pas déjà trouvés)
    if labo is None:
        name_tokens = tokens
    else:
        # Exclure le token du laboratoire trouvé
        name_tokens = [token for token in tokens if normalize(token) != normalize(labo)]
    # Compilation préalable des expressions régulières (optimisation)
    if not hasattr(parse_query, '_regex_cache'):
        parse_query._regex_cache = {
            'price_max': [
                re.compile(r'(moins de|inférieur à|<)\s*([0-9]+)'),
                re.compile(r'moins de\s*([0-9]+)')
            ],
            'price_min': [
                re.compile(r'(plus de|supérieur à|>)\s*([0-9]+)'),
                re.compile(r'plus de\s*([0-9]+)')
            ]
        }
    
    # Extraction prix max avec regex pré-compilées
    for regex in parse_query._regex_cache['price_max']:
        m = regex.search(query)
        if m:
            price_max = float(m.group(2) if len(m.groups()) > 1 else m.group(1))
            break
    
    # Extraction prix min avec regex pré-compilées
    for regex in parse_query._regex_cache['price_min']:
        m = regex.search(query)
        if m:
            price_min = float(m.group(2) if len(m.groups()) > 1 else m.group(1))
            break
    
    # Construction du nom à partir des tokens filtrés
    name = ' '.join(name_tokens).strip()
    # Nettoyage des stopwords et verbes courants avec regex pré-compilées
    if not hasattr(parse_query, '_stopwords_regex'):
        stopwords = [
            r'\bje veux\b', r'\bje cherche\b', r'\btrouver\b', r'\btrouvez\b', r'\bcherche\b', r'\brecherche\b',
            r'\bj\'aimerais\b', r'\bj\'aimerai\b', r'\bmontre\b', r'\bmontrez\b', r'\baffiche\b', r'\bafficher\b',
            r'\bmontre moi\b', r'\bmontrez moi\b', r'\baffiche moi\b', r'\bafficher moi\b',
            r'\bdu\b', r'\bde la\b', r'\bdes\b', r'\ble\b', r'\bla\b', r'\bl\''
        ]
        # Combiner tous les stopwords en une seule expression régulière
        parse_query._stopwords_regex = re.compile('|'.join(stopwords))
    
    # Appliquer le nettoyage en une seule opération
    query_clean = parse_query._stopwords_regex.sub('', query).strip()

    # Compilation préalable des expressions régulières pour l'extraction du nom
    if not hasattr(parse_query, '_name_regex'):
        parse_query._name_regex = {
            'price_indicators': re.compile(r'(moins de|inférieur à|plus de|supérieur à|entre|et|[0-9]+\s*€?|euros?)'),
            'connectors': re.compile(r'\b(à|de|chez|pour|au prix de|à partir de|à moins de|à plus de|€|euros?)\b')
        }
    
    # Extraire le nom AVANT de retirer la partie prix (avec regex pré-compilées)
    name_parts = parse_query._name_regex['price_indicators'].split(query_clean, 1)
    name = name_parts[0].strip() if name_parts else ''
    
    # Nettoyer le nom extrait des connecteurs (avec regex pré-compilées)
    if name:
        name_parts = parse_query._name_regex['connectors'].split(name, 1)
        name = name_parts[0].strip() if name_parts else name

    # Fallback optimisé : si aucun nom extrait, prendre le dernier mot
    if not name:
        words = query_clean.strip().split()
        if words:
            name = words[-1]

    # Si le nom est trop court, le vider pour éviter une recherche inutile sur "du", "le", etc.
    if len(name) < 3:
        name = ''
    print(f"[NLP DEBUG] Résultat NLP: name='{name}', labo='{labo}', price_min={price_min}, price_max={price_max}")
    # Retourne un résultat si au moins name ou labo est présent
    if not name and not labo:
        return None
    return {
        'name': name,
        'labo': labo,
        'price_min': price_min,
        'price_max': price_max
    }

@app.route('/')
def index():
    return 'API NLP OK!'

@app.route('/parse', methods=['POST'])
def parse():
    # Vérifier l'authentification
    auth_header = request.headers.get('X-Api-Secret')
    if not auth_header or auth_header != 'votre_cle_ultra_secrete':
        return jsonify({'error': 'Unauthorized access'}), 401
    
    # Récupérer et traiter la requête
    start_time = time.time()
    data = request.get_json()
    query = data.get('query', '')
    
    # Vérification rapide pour éviter le traitement complet
    if not query or len(query.strip()) < 3:
        return jsonify(None)
    
    # Analyser la requête
    result = parse_query(query)
    
    # Enregistrer le temps de traitement pour le débogage
    processing_time = time.time() - start_time
    print(f"[NLP DEBUG] Requête traitée en {processing_time:.4f} secondes: '{query}'")
    
    return jsonify(result)