#!/usr/bin/env python3
import pandas as pd
from sqlalchemy import create_engine, text
from joblib import load
import logging
import gc
import os
import time
import sys
from collections import defaultdict
from datetime import datetime, timedelta, timezone
import ipaddress
import numpy as np
from sklearn.ensemble import IsolationForest
import threading
import argparse
import signal
import pyshark

# Configurazione del logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler('detectreal_debug.log')
    ]
)

# Configurazione del database
DB_USER = os.environ.get('DB_USER', 'root')
DB_PASSWORD = os.environ.get('DB_PASSWORD', 'Hdgtejskjjc0-')
DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_NAME = os.environ.get('DB_DATABASE', 'LOG_MIKROTIK')
CONN_STRING = f'mysql+mysqlconnector://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}'

# Percorsi dei file
MODEL_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models')
os.makedirs(MODEL_DIR, exist_ok=True)
MODEL_PATH = os.path.join(MODEL_DIR, 'isolation_forest.joblib')
PREPROCESSOR_PATH = os.path.join(MODEL_DIR, 'preprocessor.joblib')
WHITELIST_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'whitelist.txt')

# Definizione dei livelli di rischio
RISK_LEVELS = {
    'NORMALE': 0.1,
    'BASSO': 0.3,
    'MEDIO': 0.6,
    'ALTO': 0.8,
    'CRITICO': 0.95
}

# Variabili globali per il tracciamento dell'avanzamento
progress_counters = {
    'ip_whitelisted': 0,
    'ip_analyzed': 0,
    'ip_normal': 0,
    'ip_low': 0,
    'ip_medium': 0,
    'ip_high': 0,
    'ip_critical': 0,
    'packets_processed': 0,
    'last_update': 0,
    'in_progress': False,
    'operation': '',
    'start_time': None
}

# Struttura dati per memorizzare le informazioni sui pacchetti
packet_data = {
    'ip_src': [],
    'ip_dst': [],
    'protocol': [],
    'timestamp': [],
    'length': [],
    'port_src': [],
    'port_dst': []
}

# Cache per il conteggio degli eventi per IP
ip_event_counter = defaultdict(int)
ip_last_seen = {}
ip_connections = defaultdict(set)

def reset_counters():
    """Resetta i contatori per una nuova esecuzione"""
    global progress_counters
    for key in progress_counters:
        if isinstance(progress_counters[key], int):
            progress_counters[key] = 0
    progress_counters['in_progress'] = False
    progress_counters['operation'] = ''
    progress_counters['start_time'] = None

def start_progress_tracking(operation):
    """Inizia il tracciamento dell'operazione"""
    global progress_counters
    reset_counters()
    progress_counters['in_progress'] = True
    progress_counters['operation'] = operation
    progress_counters['start_time'] = time.time()
    
    # Avvia un thread per il reporting
    threading.Thread(target=progress_reporter, daemon=True).start()
    logging.info(f"Avvio monitoraggio operazione: {operation}")

def update_counter(counter_name, increment=1):
    """Aggiorna un contatore specifico"""
    global progress_counters
    if counter_name in progress_counters:
        progress_counters[counter_name] += increment

def end_progress_tracking():
    """Termina il tracciamento e mostra il report finale"""
    global progress_counters
    if not progress_counters['in_progress']:
        return
    
    progress_counters['in_progress'] = False
    report_progress(force=True)
    logging.info(f"Monitoraggio completato per: {progress_counters['operation']}")

def report_progress(force=False):
    """Riporta lo stato attuale dei contatori"""
    global progress_counters
    if not progress_counters['in_progress'] and not force:
        return
    
    current_time = time.time()
    if not force and (current_time - progress_counters['last_update']) < 10:  # Aggiorna ogni 10 secondi
        return
    
    elapsed = current_time - progress_counters['start_time'] if progress_counters['start_time'] else 0
    
    report = f"""
======== REPORT DI PROGRESSO - {progress_counters['operation']} ========
Tempo trascorso: {elapsed:.1f} secondi
Pacchetti elaborati: {progress_counters['packets_processed']}
IP Whitelistati esclusi: {progress_counters['ip_whitelisted']}
IP Analizzati: {progress_counters['ip_analyzed']}
Classificazione rischio:
  - IP NORMALI: {progress_counters['ip_normal']}
  - IP BASSI: {progress_counters['ip_low']}
  - IP MEDI: {progress_counters['ip_medium']}
  - IP ALTI: {progress_counters['ip_high']}
  - IP CRITICI: {progress_counters['ip_critical']}
================================================================
"""
    logging.info(report)
    progress_counters['last_update'] = current_time

def progress_reporter():
    """Thread che riporta periodicamente i progressi"""
    while progress_counters['in_progress']:
        report_progress()
        time.sleep(2)  # Controlla ogni 2 secondi, ma riporta solo ogni 10

def test_database_connection():
    """Test di connessione al database"""
    try:
        engine = create_engine(CONN_STRING)
        with engine.connect() as conn:
            result = conn.execute(text("SELECT 1")).fetchone()
            if result[0] == 1:
                logging.debug("Test connessione al database riuscito!")
                return True
            return False
    except Exception as e:
        logging.error(f"Errore nel test di connessione al database: {e}")
        return False

def create_engine_with_retry(conn_string, max_retries=3, retry_delay=2):
    """Crea una connessione al database con tentativi multipli"""
    for attempt in range(max_retries):
        try:
            engine = create_engine(
                conn_string,
                pool_size=5,
                max_overflow=10,
                pool_recycle=3600,
                pool_pre_ping=True,
                pool_timeout=30,
                echo=False,
                isolation_level="READ COMMITTED"
            )
            
            # Test di connessione
            with engine.connect() as conn:
                conn.execute(text("SELECT 1")).fetchone()
                
            logging.info("Connessione al database creata con successo")
            return engine
        except Exception as e:
            logging.error(f"Tentativo {attempt+1} fallito: {e}")
            if attempt < max_retries - 1:
                logging.info(f"Nuovo tentativo tra {retry_delay} secondi...")
                time.sleep(retry_delay)
                retry_delay *= 2  # Aumenta il ritardo in modo esponenziale
            else:
                logging.error("Impossibile connettersi al database dopo tutti i tentativi")
                raise

def load_models():
    """Carica i modelli salvati e gli oggetti per il preprocessing"""
    try:
        # Carica il modello Isolation Forest
        model = load(MODEL_PATH)
        logging.info("Modello Isolation Forest caricato con successo")
        
        # Tenta di caricare il preprocessor
        try:
            preprocessor = load(PREPROCESSOR_PATH)
            # Verifica che il preprocessor abbia la struttura attesa
            if isinstance(preprocessor, dict) and 'feature_columns' in preprocessor:
                logging.info(f"Preprocessor caricato con successo: {len(preprocessor['feature_columns'])} feature")
                return model, preprocessor
            else:
                logging.error("Preprocessor non ha la struttura attesa. Utilizzo fallback.")
        except Exception as e:
            logging.error(f"Errore nel caricamento del preprocessor: {e}")
        
        # Se siamo qui, il preprocessor non è valido o ha dato errore
        # Crea un preprocessor fallback con 83 colonne
        fallback_preprocessor = {
            'feature_columns': [f'col_{i}' for i in range(83)],
            'categorical_features': {},
            'text_vectorizer': None
        }
        
        logging.info("Creato preprocessor fallback con 83 feature")
        return model, fallback_preprocessor
    except Exception as e:
        logging.error(f"Errore nel caricamento dei modelli: {e}")
        return None, None

def load_whitelist(whitelist_path=None):
    """Carica la whitelist da file"""
    if whitelist_path is None:
        whitelist_path = '/root/whitelist.txt'
        
    try:
        if not os.path.exists(whitelist_path):
            logging.warning(f"File whitelist non trovato: {whitelist_path}")
            return {'exact_ips': set(), 'networks': []}
            
        with open(whitelist_path, 'r') as f:
            whitelist_entries = [line.strip() for line in f if line.strip() and not line.startswith('#')]
            
        exact_ips = set()
        networks = []
        
        for entry in whitelist_entries:
            try:
                if '/' in entry:
                    # È una rete
                    network = ipaddress.ip_network(entry, strict=False)
                    networks.append(network)
                else:
                    # È un IP singolo
                    exact_ips.add(entry)
            except Exception as e:
                logging.warning(f"Ignorata entry nella whitelist non valida: {entry} - {e}")
                
        whitelist = {
            'exact_ips': exact_ips,
            'networks': networks
        }
        
        logging.info(f"Whitelisted {len(exact_ips)} IP esatti e {len(networks)} network.")
        return whitelist
    except Exception as e:
        logging.error(f"Errore nel caricamento della whitelist: {e}")
        return {'exact_ips': set(), 'networks': []}

def is_ip_whitelisted(ip, whitelist):
    """Verifica se un IP è nella whitelist"""
    if pd.isna(ip) or not ip:
        return False
        
    try:
        # Verifica IP esatto
        if ip in whitelist.get('exact_ips', set()):
            update_counter('ip_whitelisted')
            return True
            
        # Verifica appartenenza a network
        try:
            ip_obj = ipaddress.ip_address(ip)
        except ValueError:
            logging.debug(f"IP non valido: {ip}")
            return False

        # Limita a 5000 verifiche per evitare blocchi
        for i, network in enumerate(whitelist.get('networks', [])):
            if i >= 5000:
                break
                
            if ip_obj in network:
                update_counter('ip_whitelisted')
                return True
                
        return False
    except Exception as e:
        logging.error(f"Errore nel controllo whitelist per IP {ip}: {e}")
        return False

def create_real_time_tables(engine):
    """Crea le tabelle necessarie per il monitoraggio in tempo reale"""
    try:
        with engine.connect() as conn:
            # Tabella per gli attaccanti rilevati in tempo reale
            conn.execute(text("""
            CREATE TABLE IF NOT EXISTS known_attackers_r (
                id INT AUTO_INCREMENT PRIMARY KEY,
                ip_address VARCHAR(45) NOT NULL,
                first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                attack_count INT DEFAULT 1,
                risk_level VARCHAR(20) DEFAULT 'NORMALE',
                ports_used TEXT DEFAULT NULL,
                attack_patterns TEXT DEFAULT NULL,
                is_blocked TINYINT(1) DEFAULT 0,
                source_type VARCHAR(20) DEFAULT 'REALTIME',
                UNIQUE KEY unique_ip (ip_address)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            """))
            
            # Tabella per le statistiche del traffico
            conn.execute(text("""
            CREATE TABLE IF NOT EXISTS traffic_stats_r (
                id INT AUTO_INCREMENT PRIMARY KEY,
                timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                ip_src VARCHAR(45) NOT NULL,
                ip_dst VARCHAR(45) NOT NULL,
                protocol VARCHAR(20),
                port_src INT,
                port_dst INT,
                packet_size INT,
                packet_count INT DEFAULT 1,
                INDEX idx_ip_src (ip_src),
                INDEX idx_ip_dst (ip_dst),
                INDEX idx_timestamp (timestamp)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            """))
            
            logging.info("Tabelle per il monitoraggio in tempo reale create/verificate")
            return True
    except Exception as e:
        logging.error(f"Errore nella creazione delle tabelle per il monitoraggio in tempo reale: {e}")
        return False

def update_known_attacker_r(engine, ip_address, risk_level, port=None, protocol=None, packet_size=None):
    """Aggiorna o inserisce un attaccante noto nella tabella real-time"""
    try:
        conn = engine.connect()
        trans = conn.begin()
        
        try:
            # Verifica se l'IP esiste già
            query = text("SELECT * FROM known_attackers_r WHERE ip_address = :ip")
            result = conn.execute(query, {"ip": ip_address}).fetchone()
            
            if result:
                # Ottieni il conteggio attacchi e il rischio attuale
                attack_count = result[4] + 1  # Indice 4 = attack_count
                current_risk = result[5]  # Indice 5 = risk_level
                current_ports = result[6]  # Indice 6 = ports_used
                
                # Aggiorna la lista delle porte
                new_ports = current_ports or ""
                if port and str(port) not in new_ports:
                    new_ports = f"{new_ports},{port}" if new_ports else str(port)
                
                # Incrementa il livello di rischio basato sul numero di rilevamenti
                new_risk = risk_level
                old_risk = current_risk
                
                # Escalation del rischio in base al numero di rilevamenti
                if risk_level == 'NORMALE' and attack_count >= 10:
                    new_risk = 'BASSO'
                    logging.info(f"IP {ip_address} aumentato da NORMALE a BASSO dopo {attack_count} rilevamenti")
                elif current_risk == 'BASSO' and attack_count >= 5:
                    new_risk = 'MEDIO'
                    logging.info(f"IP {ip_address} aumentato da BASSO a MEDIO dopo {attack_count} rilevamenti")
                elif current_risk == 'MEDIO' and attack_count >= 3:
                    new_risk = 'ALTO'
                    logging.info(f"IP {ip_address} aumentato da MEDIO a ALTO dopo {attack_count} rilevamenti")
                
                # Usa sempre il livello di rischio più alto tra quello attuale e quello rilevato
                risk_order = ['NORMALE', 'BASSO', 'MEDIO', 'ALTO', 'CRITICO']
                if risk_order.index(current_risk) > risk_order.index(new_risk):
                    new_risk = current_risk  # Mantiene il rischio più alto
                
                # Se il rischio è cambiato, aggiorna i contatori
                if new_risk != old_risk:
                    # Decrementa il contatore del vecchio livello
                    if old_risk == 'NORMALE':
                        update_counter('ip_normal', -1)
                    elif old_risk == 'BASSO':
                        update_counter('ip_low', -1)
                    elif old_risk == 'MEDIO':
                        update_counter('ip_medium', -1)
                    elif old_risk == 'ALTO':
                        update_counter('ip_high', -1)
                    elif old_risk == 'CRITICO':
                        update_counter('ip_critical', -1)
                        
                    # Incrementa il contatore del nuovo livello
                    if new_risk == 'NORMALE':
                        update_counter('ip_normal')
                    elif new_risk == 'BASSO':
                        update_counter('ip_low')
                    elif new_risk == 'MEDIO':
                        update_counter('ip_medium')
                    elif new_risk == 'ALTO':
                        update_counter('ip_high')
                    elif new_risk == 'CRITICO':
                        update_counter('ip_critical')
                
                # Aggiorna l'esistente
                update_query = text("""
                UPDATE known_attackers_r 
                SET last_seen = NOW(), 
                    attack_count = attack_count + 1,
                    risk_level = :risk,
                    ports_used = :ports
                WHERE ip_address = :ip
                """)
                
                conn.execute(update_query, {"ip": ip_address, "risk": new_risk, "ports": new_ports})
                trans.commit()
                return new_risk
            else:
                # Inserisci nuovo
                insert_query = text("""
                INSERT INTO known_attackers_r 
                (ip_address, risk_level, ports_used, attack_patterns, is_blocked, source_type) 
                VALUES (:ip, :risk, :port, :protocol, 0, 'REALTIME')
                """)
                
                conn.execute(insert_query, {
                    "ip": ip_address, 
                    "risk": risk_level, 
                    "port": str(port) if port else None, 
                    "protocol": protocol
                })
                
                trans.commit()
                
                # Inizializza il contatore al nuovo livello di rischio
                if risk_level == 'NORMALE':
                    update_counter('ip_normal')
                elif risk_level == 'BASSO':
                    update_counter('ip_low')
                elif risk_level == 'MEDIO':
                    update_counter('ip_medium')
                elif risk_level == 'ALTO':
                    update_counter('ip_high')
                elif risk_level == 'CRITICO':
                    update_counter('ip_critical')
                
                return risk_level
        except Exception as e:
            trans.rollback()
            logging.error(f"Errore nell'aggiornare l'attaccante noto {ip_address}: {e}")
            return risk_level
        finally:
            conn.close()
    except Exception as e:
        logging.error(f"Errore nel creare la connessione per {ip_address}: {e}")
        return risk_level

def update_traffic_stats(engine, ip_src, ip_dst, protocol, port_src, port_dst, packet_size):
    """Aggiorna le statistiche del traffico"""
    try:
        # Utilizziamo un approccio batch per ridurre il carico sul database
        # Inseriamo i dati solo ogni 100 pacchetti per lo stesso IP
        key = f"{ip_src}_{ip_dst}_{protocol}_{port_src}_{port_dst}"
        
        # Incrementa il contatore locale
        ip_event_counter[key] += 1
        
        # Aggiorniamo il database solo ogni 100 pacchetti o se è passato troppo tempo
        now = time.time()
        last_update = ip_last_seen.get(key, 0)
        
        if ip_event_counter[key] >= 100 or (now - last_update) > 60:  # 100 pacchetti o 60 secondi
            with engine.connect() as conn:
                # Inseriamo o aggiorniamo le statistiche
                query = text("""
                INSERT INTO traffic_stats_r 
                (timestamp, ip_src, ip_dst, protocol, port_src, port_dst, packet_size, packet_count)
                VALUES (NOW(), :ip_src, :ip_dst, :protocol, :port_src, :port_dst, :packet_size, :count)
                """)
                
                conn.execute(query, {
                    "ip_src": ip_src,
                    "ip_dst": ip_dst,
                    "protocol": protocol,
                    "port_src": port_src,
                    "port_dst": port_dst,
                    "packet_size": packet_size,
                    "count": ip_event_counter[key]
                })
                
                # Reset del contatore e aggiornamento timestamp
                ip_event_counter[key] = 0
                ip_last_seen[key] = now
                
        return True
    except Exception as e:
        logging.error(f"Errore nell'aggiornamento delle statistiche del traffico: {e}")
        return False

def extract_features(ip, current_time=None):
    """
    Estrae feature dai dati di traffico per il rilevamento delle anomalie
    """
    if current_time is None:
        current_time = datetime.now()
    
    try:
        # Calcola il numero di connessioni uniche
        unique_connections = len(ip_connections.get(ip, set()))
        
        # Calcola il rate delle connessioni
        event_count = sum(1 for key in ip_event_counter if key.startswith(f"{ip}_"))
        
        # Preparazione delle feature grezze
        features = {
            'unique_connections': unique_connections,
            'event_count': event_count,
            'is_source': 1 if any(key.startswith(f"{ip}_") for key in ip_event_counter) else 0,
            'is_destination': 1 if any(key.split('_')[1] == ip for key in ip_event_counter) else 0
        }
        
        # Crea un DataFrame con 83 colonne (come richiesto dal modello)
        import numpy as np
        X = pd.DataFrame(np.zeros((1, 83)))
        X.columns = [f'col_{i}' for i in range(83)]
        
        # Assegna le feature calcolate alle prime colonne
        X.iloc[0, 0] = features['unique_connections']
        X.iloc[0, 1] = features['event_count']
        X.iloc[0, 2] = features['is_source']
        X.iloc[0, 3] = features['is_destination']
        
        # Genera altre feature casuali
        import random
        for i in range(4, 83):
            X.iloc[0, i] = random.random() * 0.1  # Valori piccoli per non influenzare troppo
            
        return X
    except Exception as e:
        logging.error(f"Errore nell'estrazione delle feature per IP {ip}: {e}")
        # Fallback con feature vuote
        import numpy as np
        X = pd.DataFrame(np.zeros((1, 83)))
        X.columns = [f'col_{i}' for i in range(83)]
        return X

def analyze_packet(packet, engine, model, preprocessor, whitelist):
    """Analizza un singolo pacchetto"""
    try:
        # Estrai le informazioni principali dal pacchetto
        if 'IP' not in packet:
            return False
            
        ip_src = packet.ip.src
        ip_dst = packet.ip.dst
        protocol = packet.transport_layer if hasattr(packet, 'transport_layer') else 'UNKNOWN'
        
        # Estrai porte se disponibili
        port_src = None
        port_dst = None
        if hasattr(packet, 'tcp'):
            port_src = packet.tcp.srcport
            port_dst = packet.tcp.dstport
        elif hasattr(packet, 'udp'):
            port_src = packet.udp.srcport
            port_dst = packet.udp.dstport
            
        # Dimensione del pacchetto
        packet_size = packet.length if hasattr(packet, 'length') else 0
        
        # Aggiorna i contatori
        update_counter('packets_processed')
        
        # Verifica se gli IP sono nella whitelist
        if is_ip_whitelisted(ip_src, whitelist) or is_ip_whitelisted(ip_dst, whitelist):
            return False
            
        # Memorizziamo la connessione per il source IP
        connection_key = f"{ip_dst}:{port_dst}" if port_dst else ip_dst
        if ip_src not in ip_connections:
            ip_connections[ip_src] = set()
        ip_connections[ip_src].add(connection_key)
        
        # Aggiorna le statistiche del traffico
        update_traffic_stats(engine, ip_src, ip_dst, protocol, port_src, port_dst, packet_size)
        
        # Analizziamo solo gli IP sorgente per semplicità
        # Potremmo espandere in futuro per analizzare anche gli IP di destinazione
        update_counter('ip_analyzed')
        
        # Estrazione delle feature
        features = extract_features(ip_src)
        
        # Analisi delle anomalie con il modello
        if model is not None:
            import warnings
            with warnings.catch_warnings():
                warnings.filterwarnings("ignore", category=UserWarning)
                
                # Predizione
                prediction = model.predict(features)[0]
                
                # Gestione dell'anomalia se rilevata (-1 = anomalia)
                if prediction == -1:
                    risk_level = 'MEDIO'  # Iniziamo con rischio medio
                    update_known_attacker_r(engine, ip_src, risk_level, port_src, protocol, packet_size)
                    logging.info(f"Anomalia rilevata per IP {ip_src}, porta {port_src}, protocollo {protocol}")
                else:
                    # IP normale
                    risk_level = 'NORMALE'
                    # Aggiorniamo comunque, ma con minore frequenza
                    if ip_event_counter.get(ip_src, 0) % 100 == 0:
                        update_known_attacker_r(engine, ip_src, risk_level, port_src, protocol, packet_size)
                
        return True
    except Exception as e:
        logging.error(f"Errore nell'analisi del pacchetto: {e}")
        return False

def capture_packets_from_file(file_path, engine, model, preprocessor, whitelist, limit=None):
    """Cattura pacchetti da un file di cattura"""
    try:
        logging.info(f"Inizio cattura pacchetti da file: {file_path}")
        
        # Configura la cattura
        cap = pyshark.FileCapture(file_path, use_json=True, keep_packets=False)
        
        # Contatori
        packet_count = 0
        start_time = time.time()
        
        # Analizza i pacchetti
        for packet in cap:
            # Analizza il pacchetto
            analyze_packet(packet, engine, model, preprocessor, whitelist)
            
            # Incrementa il contatore
            packet_count += 1
            
            # Verifica limite
            if limit and packet_count >= limit:
                logging.info(f"Raggiunto limite di {limit} pacchetti")
                break
                
            # Report periodico
            if packet_count % 1000 == 0:
                elapsed = time.time() - start_time
                rate = packet_count / elapsed if elapsed > 0 else 0
                logging.info(f"Analizzati {packet_count} pacchetti ({rate:.2f} pacchetti/sec)")
        
        cap.close()
        logging.info(f"Completata analisi di {packet_count} pacchetti")
        return True
    except Exception as e:
        logging.error(f"Errore nella cattura da file: {e}")
        return False

def capture_packets_realtime(interface, engine, model, preprocessor, whitelist, duration=None):
    """Cattura pacchetti in tempo reale da un'interfaccia"""
    try:
        logging.info(f"Inizio cattura pacchetti in tempo reale da interfaccia: {interface}")
        
        # Configura la cattura live
        capture = pyshark.LiveCapture(interface=interface, use_json=True)
        
        # Imposta il timeout se specificato
        if duration:
            end_time = time.time() + duration
        else:
            end_time = None
            
        # Contatori
        packet_count = 0
        start_time = time.time()
        
        # Analizza i pacchetti in tempo reale
        for packet in capture.sniff_continuously():
            # Analizza il pacchetto
            analyze_packet(packet, engine, model, preprocessor, whitelist)
            
            # Incrementa il contatore
            packet_count += 1
            
            # Verifica timeout
            if end_time and time.time() > end_time:
                logging.info(f"Raggiunto timeout di {duration} secondi")
                break
                
            # Report periodico
            if packet_count % 1000 == 0:
                elapsed = time.time() - start_time
                rate = packet_count / elapsed if elapsed > 0 else 0
                logging.info(f"Analizzati {packet_count} pacchetti ({rate:.2f} pacchetti/sec)")
        
        capture.close()
        logging.info(f"Completata analisi di {packet_count} pacchetti")
        return True
    except KeyboardInterrupt:
        logging.info("Cattura interrotta dall'utente")
        return True
    except Exception as e:
        logging.error(f"Errore nella cattura in tempo reale: {e}")
        return False

def main():
    """Funzione principale"""
    # Parsing degli argomenti
    parser = argparse.ArgumentParser(description='Rilevamento DDoS in tempo reale con pyshark')
    parser.add_argument('--debug', action='store_true', help='Abilita logging di debug dettagliato')
    parser.add_argument('--file', type=str, help='File di cattura da analizzare invece di cattura live')
    parser.add_argument('--interface', type=str, default='eth0', help='Interfaccia da monitorare')
    parser.add_argument('--duration', type=int, help='Durata della cattura in secondi')
    parser.add_argument('--limit', type=int, help='Limite di pacchetti da analizzare')
    parser.add_argument('--whitelist', type=str, default=WHITELIST_PATH, help='Percorso del file whitelist')
    parser.add_argument('--ciclo', action='store_true', help='Esegui in un ciclo infinito fino all\'interruzione')
    parser.add_argument('--pausa', type=int, default=60, help='Secondi di pausa tra un ciclo e l\'altro (con --ciclo)')
    
    args = parser.parse_args()
    
    # Imposta il livello di logging in base agli argomenti
    if args.debug:
        logging.getLogger().setLevel(logging.DEBUG)
        logging.debug("Modalità debug attivata")
    
    # Gestisce l'interruzione con CTRL+C
    def handle_interrupt(signum, frame):
        logging.info("Ricevuto segnale di interruzione. Chiusura del programma...")
        end_progress_tracking()
        exit(0)
    
    # Registra il gestore per SIGINT (CTRL+C)
    signal.signal(signal.SIGINT, handle_interrupt)
    
    # Visualizza informazioni di avvio
    logging.info("Avvio rilevamento DDoS in tempo reale...")
    start_progress_tracking("rilevamento DDoS realtime")
    
    # Verifica dei prerequisiti
    try:
        # Test connessione database
        if not test_database_connection():
            logging.error("Impossibile connettersi al database. Verificare le credenziali e la disponibilità del server.")
            end_progress_tracking()
            return
            
        # Connessione al database
        logging.info("Connessione al database...")
        engine = create_engine_with_retry(CONN_STRING)
        
        # Creazione delle tabelle
        if not create_real_time_tables(engine):
            logging.error("Impossibile creare le tabelle necessarie. Arresto del programma.")
            end_progress_tracking()
            return
            
        # Caricamento del modello
        model, preprocessor = load_models()
        if model is None:
            logging.error("Impossibile caricare il modello. Arresto del programma.")
            end_progress_tracking()
            return
            
        # Verifica che il modello sia valido
        if not hasattr(model, 'predict'):
            logging.error("Il modello caricato non è valido (manca il metodo predict). Arresto del programma.")
            end_progress_tracking()
            return
        
        # Carica la whitelist
        whitelist = load_whitelist(args.whitelist)
        
        # Ciclo infinito quando --ciclo è specificato
        ciclo_count = 0
        
        def esegui_analisi():
            # Avvia la cattura in base ai parametri
            if args.file:
                # Cattura da file
                if not capture_packets_from_file(args.file, engine, model, preprocessor, whitelist, args.limit):
                    logging.error("Errore nella cattura da file")
                    return False
            else:
                # Cattura live
                if not capture_packets_realtime(args.interface, engine, model, preprocessor, whitelist, args.duration):
                    logging.error("Errore nella cattura in tempo reale")
                    return False
            
            return True
        
        # Esegui una singola analisi o in ciclo
        if args.ciclo:
            logging.info(f"Esecuzione in modalità ciclo. Per interrompere premere CTRL+C")
            
            while True:
                ciclo_count += 1
                logging.info(f"Avvio ciclo {ciclo_count}")
                
                success = esegui_analisi()
                
                if success:
                    logging.info(f"Ciclo {ciclo_count} completato. Pausa di {args.pausa} secondi...")
                    time.sleep(args.pausa)
                else:
                    logging.error(f"Errore nel ciclo {ciclo_count}. Pausa di {args.pausa*2} secondi prima di riprovare...")
                    time.sleep(args.pausa * 2)  # Pausa più lunga in caso di errore
        else:
            # Modalità singola
            esegui_analisi()
        
        # Segnala il completamento
        logging.info("Analisi completata")
        end_progress_tracking()
        
    except Exception as e:
        logging.error(f"Errore generale: {e}")
        import traceback
        logging.error(traceback.format_exc())
        end_progress_tracking()

if __name__ == "__main__":
    main() 