import os
import re
import time
import threading
from typing import List, Optional, Callable, Any
import indexer
import db

def get_directories_total_size(paths: List[str]) -> int:
    """Calcula el tamaño total combinado en bytes de uno o más directorios"""
    total = 0
    for path in paths:
        if not path or not os.path.exists(path):
            continue
        try:
            for root, _, files in os.walk(path):
                for f in files:
                    fp = os.path.join(root, f)
                    try:
                        total += os.path.getsize(fp)
                    except (OSError, FileNotFoundError):
                        pass
        except Exception as e:
            print(f"[WATCHER] Error calculando tamaño de directorio {path}: {e}")
    return total

class StorageWatcher:
    def __init__(self, storage_paths: Optional[Any] = None, interval_seconds: int = 10, debounce_seconds: int = 2):
        if storage_paths is None:
            self.storage_paths = indexer.get_configured_storage_paths()
        elif isinstance(storage_paths, str):
            self.storage_paths = [p.strip() for p in re.split(r'[,;]', storage_paths) if p.strip()]
        elif isinstance(storage_paths, (list, tuple)):
            self.storage_paths = list(storage_paths)
        else:
            self.storage_paths = indexer.get_configured_storage_paths()

        self.interval = interval_seconds
        self.debounce = debounce_seconds
        self.running = False
        self.thread: Optional[threading.Thread] = None
        self.last_size: int = 0
        self.lock = threading.Lock()

    def start(self, run_initial_scan: bool = True):
        if self.running:
            return
        self.running = True
        self.thread = threading.Thread(target=self._run_loop, args=(run_initial_scan,), daemon=True)
        self.thread.start()
        print(f"[WATCHER] Observador multiruta iniciado en {len(self.storage_paths)} directorio(s): {self.storage_paths} (Intervalo: {self.interval}s)")

    def stop(self):
        self.running = False
        if self.thread and self.thread.is_alive():
            self.thread.join(timeout=3.0)
        print("[WATCHER] Observador detenido.")

    def trigger_scan_now(self):
        """Dispara un escaneo manual inmediato de todos los directorios"""
        with self.lock:
            res = indexer.scan_storage_directory(self.storage_paths)
            self.last_size = get_directories_total_size(self.storage_paths)
            db.set_meta("total_dir_size_bytes", str(self.last_size))
            return res

    def update_config(self, interval_seconds: Optional[int] = None, enabled: Optional[bool] = None, storage_paths: Optional[List[str]] = None) -> dict:
        """Permite actualizar el intervalo, activar/desactivar o cambiar rutas en caliente"""
        if interval_seconds is not None and interval_seconds > 0:
            self.interval = int(interval_seconds)
            print(f"[WATCHER] Intervalo actualizado en caliente a: {self.interval}s")

        if storage_paths is not None:
            self.storage_paths = storage_paths
            print(f"[WATCHER] Rutas actualizadas en caliente a: {self.storage_paths}")

        if enabled is not None:
            if enabled and not self.running:
                print("[WATCHER] Reanudando observador en caliente...")
                self.start(run_initial_scan=False)
            elif not enabled and self.running:
                print("[WATCHER] Pausando observador en caliente...")
                self.stop()

        return {
            "enabled": self.running,
            "interval_seconds": self.interval,
            "storage_paths": self.storage_paths
        }


    def _run_loop(self, run_initial_scan: bool):
        # 1. Escaneo inicial al arrancar
        if run_initial_scan:
            try:
                print(f"[WATCHER] Ejecutando escaneo inicial de indexación sobre {len(self.storage_paths)} rutas...")
                indexer.scan_storage_directory(self.storage_paths)
            except Exception as e:
                print(f"[WATCHER] Error en escaneo inicial: {e}")

        self.last_size = get_directories_total_size(self.storage_paths)
        db.set_meta("total_dir_size_bytes", str(self.last_size))

        while self.running:
            try:
                time.sleep(self.interval)
                if not self.running:
                    break

                current_size = get_directories_total_size(self.storage_paths)
                
                # Detectar si el tamaño combinado cambió o creció en alguna carpeta
                if current_size != self.last_size:
                    print(f"[WATCHER] Cambio detectado en almacenamiento: tamaño previo={self.last_size} bytes, nuevo={current_size} bytes. Esperando debounce de {self.debounce}s...")
                    time.sleep(self.debounce)
                    
                    # Re-verificar tamaño tras debounce
                    current_size = get_directories_total_size(self.storage_paths)
                    with self.lock:
                        print(f"[WATCHER] Disparando indexador por cambio de tamaño en carpetas...")
                        indexer.scan_storage_directory(self.storage_paths)
                        self.last_size = current_size
                        db.set_meta("total_dir_size_bytes", str(self.last_size))

            except Exception as e:
                print(f"[WATCHER] Error en ciclo de monitoreo: {e}")
                time.sleep(self.interval)

if __name__ == "__main__":
    watcher = StorageWatcher(interval_seconds=5)
    watcher.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        watcher.stop()
