"""
ENIGMA Inventario - Inventory Service FIXED
============================================

Service completo con:
- Token Cassanova in Flask session (PERSISTENTE)
- Integrazione con models che usano tabelle reali
- Cache prodotti locale
- Workflow completo inventario

Author: HetGi & Claude
Date: 2026-01-14 (FIXED VERSION)
"""

import requests
import json
from datetime import datetime, timedelta
from typing import Optional, Dict, List, Any
from flask import session
import logging

logger = logging.getLogger(__name__)


class CassanovaService:
    """Service per API Cassanova con token in Flask session."""
    
    def __init__(self, api_key: str, hostname: str = "https://api.cassanova.com"):
        self.api_key = api_key
        self.hostname = hostname
        self.api_version = "1.0.0"
    
    def _get_token(self) -> str:
        """
        Ottiene token da Flask session o ne richiede uno nuovo.
        
        Returns:
            Token valido
        """
        try:
            # Controlla sessione Flask (se disponibile)
            token = session.get('cassanova_token')
            expires_at = session.get('cassanova_token_expires')
            
            if token and expires_at:
                expires_dt = datetime.fromisoformat(expires_at)
                if datetime.now() < (expires_dt - timedelta(seconds=60)):
                    logger.debug("Token valido in sessione Flask")
                    return token
        except RuntimeError:
            # Session non disponibile (fuori da request context)
            # Fallback: richiedi sempre nuovo token
            logger.debug("Flask session non disponibile, richiedo nuovo token")
        
        # Richiedi nuovo token
        logger.info("Richiesta nuovo token Cassanova...")
        
        url = f"{self.hostname}/apikey/token"
        headers = {
            "Content-Type": "application/json",
            "X-Requested-With": "*"
        }
        payload = {"apiKey": self.api_key}
        
        response = requests.post(url, headers=headers, json=payload, timeout=10)
        response.raise_for_status()
        
        data = response.json()
        new_token = data['access_token']
        expires_in = data.get('expires_in', 3600)
        
        # Salva in Flask session (se disponibile)
        try:
            session['cassanova_token'] = new_token
            session['cassanova_token_expires'] = (
                datetime.now() + timedelta(seconds=expires_in)
            ).isoformat()
            logger.info(f"Nuovo token salvato in session, scade tra {expires_in}s")
        except RuntimeError:
            logger.debug("Session non disponibile, token non salvato")
        
        return new_token
    
    def _request(self, method: str, endpoint: str, **kwargs) -> Dict:
        """
        Esegue richiesta HTTP con autenticazione.
        
        Args:
            method: GET, POST, etc.
            endpoint: /products, etc.
            **kwargs: params, json, etc.
        
        Returns:
            Response JSON
        """
        token = self._get_token()
        
        url = f"{self.hostname}{endpoint}"
        headers = {
            "Content-Type": "application/json",
            "X-Version": self.api_version,
            "Authorization": f"Bearer {token}"
        }
        
        response = requests.request(method, url, headers=headers, timeout=30, **kwargs)
        response.raise_for_status()
        return response.json()
    
    def search_product_by_barcode(self, barcode: str) -> Optional[Dict]:
        """Cerca prodotto per barcode."""
        try:
            data = self._request('GET', '/products', params={'barcode': barcode, 'start': 0, 'limit': 1})
            products = data.get('products', [])
            return products[0] if products else None
        except Exception as e:
            logger.error(f"Errore ricerca barcode: {e}")
            return None
    
    def search_products(self, description: str = None, start: int = 0, limit: int = 100) -> Dict:
        """Cerca prodotti."""
        try:
            params = {'start': start, 'limit': limit}
            if description:
                params['description'] = description
            return self._request('GET', '/products', params=params)
        except Exception as e:
            logger.error(f"Errore ricerca prodotti: {e}")
            return {'totalCount': 0, 'products': []}
    
    def get_all_products(self, batch_size: int = 100) -> List[Dict]:
        """Scarica TUTTI i prodotti (paginato)."""
        all_products = []
        start = 0
        
        try:
            while True:
                data = self.search_products(start=start, limit=batch_size)
                products = data.get('products', [])
                total = data.get('totalCount', 0)
                
                if not products:
                    break
                
                all_products.extend(products)
                logger.info(f"Scaricati {len(all_products)}/{total} prodotti...")
                
                if len(all_products) >= total:
                    break
                
                start += batch_size
            
            logger.info(f"Download completato: {len(all_products)} prodotti")
            return all_products
        except Exception as e:
            logger.error(f"Errore download prodotti: {e}")
            return all_products
    
    def get_stocks(self, salespoint_id: int, batch_size: int = 100) -> Dict:
        """
        Scarica TUTTE le giacenze per un salespoint (paginato).

        Args:
            salespoint_id: ID del punto vendita
            batch_size: Numero di risultati per pagina

        Returns:
            {'stocks': [...], 'totalCount': N}
        """
        all_stocks = []
        start = 0

        try:
            logger.info(f"INIZIO get_stocks per salespoint {salespoint_id}")

            while True:
                data = self._request(
                    'GET',
                    f'/stocks/{salespoint_id}',
                    params={'start': start, 'limit': batch_size}
                )
                stocks = data.get('stocks', [])
                total = data.get('totalCount', 0)

                if not stocks:
                    break

                all_stocks.extend(stocks)
                logger.info(f"Scaricate {len(all_stocks)}/{total} giacenze per salespoint {salespoint_id}...")

                if len(all_stocks) >= total:
                    break

                start += batch_size

            logger.info(f"Download giacenze completato per salespoint {salespoint_id}: {len(all_stocks)} items")
            return {'stocks': all_stocks, 'totalCount': len(all_stocks)}
        except Exception as e:
            logger.error(f"Errore get_stocks: {e}")
            return {'stocks': all_stocks, 'totalCount': len(all_stocks)}


def create_inventory_service(db_config: Dict, cassanova_api_key: str):
    """
    Factory per creare InventoryService.
    
    Args:
        db_config: {host, user, password, database}
        cassanova_api_key: API Key Cassanova
    
    Returns:
        InventoryService instance
    """
    from app.models.inventory import InventorySession, InventoryItem, ProductCache
    
    class InventoryService:
        """Service completo inventario."""
        
        def __init__(self):
            self.session_model = InventorySession(db_config)
            self.item_model = InventoryItem(db_config)
            self.cache_model = ProductCache(db_config)
            self.cassanova = CassanovaService(api_key=cassanova_api_key)
        
        # ====================================================================
        # SINCRONIZZAZIONE PRODOTTI
        # ====================================================================
        
        def sync_all_products(self, tenant_id: int) -> Dict[str, Any]:
            """
            Sincronizza TUTTI i prodotti da Cassanova alla cache locale.
            Usa batch insert per performance (singola connessione DB).

            Args:
                tenant_id: ID tenant

            Returns:
                {
                    'total': int,
                    'cached': int,
                    'errors': int
                }
            """
            import os
            logger.info(f"Inizio sincronizzazione prodotti per tenant {tenant_id}")

            products = self.cassanova.get_all_products()
            logger.info(f"Scaricati {len(products)} prodotti da Cassanova")

            ttl = int(os.getenv('CACHE_TTL_SECONDS', 604800))
            items_to_cache = []
            products_with_barcodes = 0

            for idx, product in enumerate(products):
                barcodes_list = product.get('barcodes', [])
                had_barcodes = False

                if idx < 3:
                    logger.info(f"Prodotto {idx}: {product.get('description')}, barcodes: {barcodes_list}")

                if barcodes_list:
                    products_with_barcodes += 1
                    for barcode_obj in barcodes_list:
                        barcode_value = barcode_obj.get('value') if isinstance(barcode_obj, dict) else barcode_obj
                        if barcode_value:
                            had_barcodes = True
                            product_json = json.dumps(product)
                            items_to_cache.append((tenant_id, str(barcode_value), product_json, ttl))

                # Gestisci anche prodotti multivariant (hanno barcodes nelle variants)
                if product.get('multivariant') and product.get('variants'):
                    for variant in product.get('variants', []):
                        variant_barcodes = variant.get('barcodes', [])
                        for barcode_obj in variant_barcodes:
                            barcode_value = barcode_obj.get('value') if isinstance(barcode_obj, dict) else barcode_obj
                            if barcode_value:
                                had_barcodes = True
                                product_with_variant = product.copy()
                                product_with_variant['selected_variant'] = variant
                                product_json = json.dumps(product_with_variant)
                                items_to_cache.append((tenant_id, str(barcode_value), product_json, ttl))

                # Prodotti senza barcode: usa product ID come chiave
                if not had_barcodes and product.get('id'):
                    product_json = json.dumps(product)
                    items_to_cache.append((tenant_id, str(product['id']), product_json, ttl))

            logger.info(f"Preparati {len(items_to_cache)} barcode da {products_with_barcodes} prodotti per batch insert")

            cached = self.cache_model.cache_products_batch(items_to_cache)
            errors = len(items_to_cache) - cached

            result = {
                'total': len(products),
                'cached': cached,
                'errors': errors
            }

            logger.info(f"Sincronizzazione completata: {result}")
            logger.info(f"Prodotti con barcodes: {products_with_barcodes}")
            return result
        
        # ====================================================================
        # RICERCA PRODOTTI
        # ====================================================================
        

        def search_product(self, tenant_id: int, barcode: str = None, 
                          description: str = None) -> List[Dict]:
            """
            Cerca prodotti per barcode/ID o nome.
            
            IMPORTANTE: Usa SOLO cache locale (nessuna chiamata API).
            I prodotti devono essere pre-sincronizzati tramite sync_all_products().
            
            Args:
                tenant_id: ID tenant
                barcode: Codice a barre o ID prodotto (ricerca esatta)
                description: Nome prodotto (ricerca parziale)
            
            Returns:
                Lista prodotti trovati (dalla cache locale)
            """
            if barcode:
                # RICERCA PER BARCODE: SOLO CACHE LOCALE
                cached = self.cache_model.get_cached_product(tenant_id, barcode)
                if cached:
                    logger.debug(f"Prodotto {barcode} trovato in cache locale")
                    return [cached]
                
                # Non trovato in cache - ritorna vuoto
                logger.warning(f"Barcode {barcode} non trovato in cache locale. Sincronizza dalla dashboard.")
                return []
            
            elif description:
                # RICERCA PER NOME: SOLO CACHE LOCALE
                products = self.cache_model.search_products_local(tenant_id, description, limit=20)
                return products
            
            else:
                return []
        
        # ====================================================================
        # SALES POINTS (SEDI)
        # ====================================================================
        
        def get_salespoints(self, tenant_id: int) -> List[Dict]:
            """
            Recupera tutti i Sales Points disponibili dai prodotti sincronizzati.
            
            Args:
                tenant_id: ID tenant
            
            Returns:
                Lista sales points con conteggio prodotti
            """
            import pymysql
            
            conn = pymysql.connect(**self.session_model.db_config)
            cursor = conn.cursor()
            
            try:
                # Estrai sales points unici dai prodotti in cache
                sql = """
                    SELECT 
                        JSON_EXTRACT(dati_cassanova, '$.idSalesPoint') as sp_id,
                        COUNT(*) as product_count
                    FROM inv_cache_prodotti
                    WHERE id_tenant = %s
                    GROUP BY sp_id
                    ORDER BY product_count DESC
                """
                cursor.execute(sql, (tenant_id,))
                results = cursor.fetchall()
                
                salespoints = []
                for row in results:
                    sp_id = row[0]
                    if sp_id:
                        # Verifica se esiste in inv_sedi
                        cursor.execute("""
                            SELECT nome, indirizzo, città 
                            FROM inv_sedi 
                            WHERE id = %s AND id_tenant = %s
                        """, (sp_id, tenant_id))
                        
                        sede_data = cursor.fetchone()
                        
                        if sede_data:
                            name = sede_data[0]
                            address = f"{sede_data[1]}, {sede_data[2]}" if sede_data[1] else None
                        else:
                            name = f"Sede {sp_id}"
                            address = None
                        
                        salespoints.append({
                            'id': int(sp_id),
                            'name': name,
                            'address': address,
                            'product_count': row[1]
                        })
                
                return salespoints
                
            finally:
                cursor.close()
                conn.close()
        
        def update_session_name(self, session_id: int, tenant_id: int, name: str) -> bool:
            """Aggiorna nome sessione."""
            return self.session_model.update_session_name(session_id, tenant_id, name)
        
        # ====================================================================
        # SESSIONI INVENTARIO
        # ====================================================================
        
        def start_session(self, tenant_id: int, user_id: int, sede_id: int = 17117) -> Optional[int]:
            """Crea nuova sessione."""
            return self.session_model.create_session(tenant_id, user_id, sede_id)
        
        def get_current_session(self, tenant_id: int, user_id: int, sede_id: int = None) -> Optional[Dict]:
            """Ottiene sessione aperta corrente per una sede."""
            return self.session_model.get_active_session(tenant_id, user_id, sede_id)
        
        def get_session_details(self, session_id: int, tenant_id: int) -> Optional[Dict]:
            """Ottiene dettagli sessione."""
            return self.session_model.get_session(session_id, tenant_id)
        
        def close_session(self, session_id: int, tenant_id: int, note: str = None) -> bool:
            """Chiude sessione."""
            return self.session_model.close_session(session_id, tenant_id, note)
        
        def get_recent_sessions(self, tenant_id: int, limit: int = 10) -> List[Dict]:
            """Lista sessioni recenti."""
            return self.session_model.get_recent_sessions(tenant_id, limit)
        
        # ====================================================================
        # ITEM INVENTARIO
        # ====================================================================
        
        def add_item(self, session_id: int, tenant_id: int, barcode: str,
                    product_data: Optional[Dict] = None, found_in_api: bool = False,
                    quantita: int = 1, id_sede: int = None) -> Optional[int]:
            """Aggiunge item a sessione."""
            return self.item_model.add_item(
                session_id, tenant_id, barcode,
                product_data, found_in_api, quantita, id_sede
            )
        
        def get_session_items(self, session_id: int, tenant_id: int, id_sede: int = None) -> List[Dict]:
            """Ottiene items di una sessione filtrati per sede."""
            return self.item_model.get_session_items(session_id, tenant_id, id_sede)
        
        def delete_item(self, item_id: int, session_id: int) -> bool:
            """Elimina item da sessione."""
            return self.item_model.delete_item(item_id, session_id)
        
        def update_item_quantity(self, item_id: int, session_id: int, new_quantity: int) -> bool:
            """Modifica quantitÃ  item."""
            return self.item_model.update_item_quantity(item_id, session_id, new_quantity)
        
        def sync_stocks(self, tenant_id: int, salespoint_id: int) -> int:
            """
            Sincronizza giacenze da Cassanova per un salespoint.
            
            Args:
                tenant_id: ID tenant
                salespoint_id: ID sede da sincronizzare
            
            Returns:
                Numero di giacenze sincronizzate
            """
            try:
                # Chiama API Cassanova per get stocks
                import pymysql
                import json
                from datetime import datetime
                
                # Get stocks da Cassanova
                response = self.cassanova.get_stocks(salespoint_id)
                stocks = response.get('stocks', [])
                
                conn = pymysql.connect(**self.session_model.db_config)
                cursor = conn.cursor()
                
                synced = 0
                for stock in stocks:
                    sql = """
                        INSERT INTO inv_giacenze (
                            id_tenant, id_sede, id_prodotto, id_variante,
                            quantita, quantita_in_arrivo, quantita_in_uscita,
                            soglia_minima, gestisci_giacenza, data_sincronizzazione
                        ) VALUES (
                            %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW()
                        )
                        ON DUPLICATE KEY UPDATE
                            quantita = VALUES(quantita),
                            quantita_in_arrivo = VALUES(quantita_in_arrivo),
                            quantita_in_uscita = VALUES(quantita_in_uscita),
                            soglia_minima = VALUES(soglia_minima),
                            gestisci_giacenza = VALUES(gestisci_giacenza),
                            data_sincronizzazione = NOW()
                    """
                    
                    cursor.execute(sql, (
                        tenant_id,
                        salespoint_id,
                        stock.get('idProduct'),
                        stock.get('idProductVariant'),
                        stock.get('quantity', 0),
                        stock.get('incomingQuantity', 0),
                        stock.get('outgoingQuantity', 0),
                        stock.get('warningLevel', 0),
                        stock.get('manageStock', True)
                    ))
                    synced += 1
                
                conn.commit()
                cursor.close()
                conn.close()
                
                logger.info(f"Sincronizzate {synced} giacenze per sede {salespoint_id}")
                return synced
                
            except Exception as e:
                logger.error(f"Errore sync_stocks: {e}")
                return 0
    
    return InventoryService()


