"""
Project Discovery Service
Servicio para descubrir proyectos S7 usando Everything API
"""

import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Dict, Any, Optional
import logging

from models.project_model import Project, ProjectManager
from models.config_model import Config
from utils.everything_auto_detector import create_universal_everything_searcher


class ProjectDiscoveryService:
    """Servicio para descubrir y gestionar proyectos"""

    def __init__(self, config: Config, project_manager: ProjectManager):
        self.config = config
        self.project_manager = project_manager
        self.logger = logging.getLogger(__name__)

        # Inicializar Everything searcher
        dll_path = config.get_dll_path()
        self.everything_searcher = create_universal_everything_searcher(dll_path)

        if not self.everything_searcher:
            self.logger.warning("Everything API no disponible, funcionalidad limitada")

    def discover_all_projects(self) -> List[Project]:
        """
        Descubrir todos los proyectos en los directorios de observación
        """
        self.logger.info("Iniciando descubrimiento de proyectos...")

        discovered_projects = []

        # Obtener directorios de observación habilitados
        observation_dirs = [
            obs_dir
            for obs_dir in self.config.observation_directories
            if obs_dir.get("enabled", True)
        ]

        for obs_dir in observation_dirs:
            dir_path = obs_dir["path"]
            dir_type = obs_dir["type"]

            self.logger.info(f"Escaneando directorio: {dir_path} (tipo: {dir_type})")

            if dir_type == "siemens_s7":
                projects = self._discover_s7_projects(obs_dir)
            else:  # manual directories
                projects = self._discover_manual_projects(obs_dir)

            discovered_projects.extend(projects)

        self.logger.info(
            f"Descubrimiento completado: {len(discovered_projects)} proyectos encontrados"
        )

        # Actualizar project manager con proyectos descubiertos
        self._update_project_manager(discovered_projects)

        return discovered_projects

    def _discover_s7_projects(self, obs_dir: Dict[str, Any]) -> List[Project]:
        """Descubrir proyectos S7 en un directorio de observación"""
        projects = []
        dir_path = obs_dir["path"]

        try:
            # Verificar si Everything API está configurado y disponible
            use_everything = self.config.everything_api.get("enabled", False)

            if (
                use_everything
                and self.everything_searcher
                and self.everything_searcher.is_everything_available()
            ):
                self.logger.info(f"🔍 Usando Everything API para {dir_path}")
                try:
                    s7p_files = self.everything_searcher.search_s7p_files([dir_path])

                    if s7p_files:
                        self.logger.info(
                            f"✅ Everything API encontró {len(s7p_files)} archivos"
                        )
                    else:
                        self.logger.warning(
                            "⚠️  Everything API no encontró archivos, usando búsqueda manual"
                        )
                        s7p_files = self._manual_search_s7p_files(dir_path)
                except Exception as e:
                    self.logger.error(f"❌ Error con Everything API: {e}")
                    self.logger.info("🔍 Usando búsqueda manual como fallback")
                    s7p_files = self._manual_search_s7p_files(dir_path)
            else:
                # Usar búsqueda manual directamente
                reason = (
                    "Everything API deshabilitado"
                    if not use_everything
                    else "Everything API no disponible"
                )
                self.logger.info(
                    f"🔍 Usando búsqueda manual para {dir_path} ({reason})"
                )
                s7p_files = self._manual_search_s7p_files(dir_path)

            self.logger.debug(
                f"Encontrados {len(s7p_files)} archivos .s7p en {dir_path}"
            )

            for s7p_file in s7p_files:
                project = self._create_project_from_s7p(s7p_file, obs_dir)
                if project:
                    projects.append(project)
                    self.logger.debug(f"Proyecto creado: {project.name}")

        except Exception as e:
            self.logger.error(f"Error descubriendo proyectos S7 en {dir_path}: {e}")
            # Como último recurso, intentar búsqueda manual
            try:
                self.logger.info("Intentando búsqueda manual como último recurso")
                s7p_files = self._manual_search_s7p_files(dir_path)
                for s7p_file in s7p_files:
                    project = self._create_project_from_s7p(s7p_file, obs_dir)
                    if project:
                        projects.append(project)
            except Exception as fallback_error:
                self.logger.error(
                    f"Error en búsqueda manual fallback: {fallback_error}"
                )

        return projects

    def _discover_manual_projects(self, obs_dir: Dict[str, Any]) -> List[Project]:
        """Descubrir proyectos de directorio manual"""
        projects = []
        dir_path = obs_dir["path"]

        try:
            # Para directorios manuales, crear un proyecto por directorio
            if Path(dir_path).exists() and Path(dir_path).is_dir():
                project = self._create_manual_project(obs_dir)
                if project:
                    projects.append(project)
                    self.logger.debug(f"Proyecto manual creado: {project.name}")

        except Exception as e:
            self.logger.error(f"Error creando proyecto manual para {dir_path}: {e}")

        return projects

    def _manual_search_s7p_files(self, directory: str) -> List[str]:
        """Búsqueda manual de archivos .s7p como fallback"""
        s7p_files = []

        try:
            path = Path(directory)
            if not path.exists():
                return s7p_files

            # Búsqueda recursiva con optimización (evitar último nivel)
            skip_last_level = self.config.everything_api.get(
                "skip_last_level_for_s7p", True
            )

            for file_path in path.rglob("*.s7p"):
                if skip_last_level:
                    # Verificar que no esté en el último nivel (debe tener subdirectorios)
                    parent_dir = file_path.parent
                    has_subdirs = any(item.is_dir() for item in parent_dir.iterdir())
                    if not has_subdirs:
                        continue  # Saltar archivos en directorios sin subdirectorios

                s7p_files.append(str(file_path))

        except Exception as e:
            self.logger.error(f"Error en búsqueda manual de S7P en {directory}: {e}")

        return s7p_files

    def _create_project_from_s7p(
        self, s7p_file_path: str, obs_dir: Dict[str, Any]
    ) -> Optional[Project]:
        """Crear un objeto Project desde un archivo .s7p"""
        try:
            s7p_path = Path(s7p_file_path)
            project_dir = s7p_path.parent
            obs_path = Path(obs_dir["path"])

            # Generar ID único para el proyecto
            project_id = self._generate_project_id(s7p_file_path)

            # Determinar nombre del proyecto
            project_name = s7p_path.stem

            # Calcular ruta relativa
            try:
                relative_path = project_dir.relative_to(obs_path)
                backup_path = str(relative_path)
            except ValueError:
                # Si no se puede calcular relativa, usar nombre del directorio
                backup_path = project_dir.name
                relative_path = Path(project_dir.name)

            # Crear datos del proyecto
            project_data = {
                "id": project_id,
                "name": project_name,
                "path": str(project_dir),
                "type": "siemens_s7",
                "s7p_file": s7p_file_path,
                "observation_directory": obs_dir["path"],
                "relative_path": str(relative_path),
                "backup_path": backup_path,
                "schedule_config": {
                    "schedule": self.config.global_settings.get(
                        "default_schedule", "daily"
                    ),
                    "schedule_time": self.config.global_settings.get(
                        "default_schedule_time", "02:00"
                    ),
                    "enabled": True,
                    "next_scheduled_backup": "",
                },
                "backup_history": {
                    "last_backup_date": "",
                    "last_backup_file": "",
                    "backup_count": 0,
                    "last_successful_backup": "",
                },
                "hash_info": {
                    "last_s7p_hash": "",
                    "last_full_hash": "",
                    "last_s7p_timestamp": "",
                    "last_s7p_size": 0,
                    "last_scan_timestamp": "",
                    "file_count": 0,
                    "total_size_bytes": 0,
                },
                "status": {
                    "current_status": "ready",
                    "last_error": None,
                    "retry_count": 0,
                    "next_retry": None,
                    "files_in_use": False,
                    "exclusivity_check_passed": True,
                    "last_status_update": datetime.now(timezone.utc).isoformat(),
                },
                "discovery_info": {
                    "discovered_date": datetime.now(timezone.utc).isoformat(),
                    "discovery_method": (
                        "everything_api"
                        if self.everything_searcher
                        else "manual_search"
                    ),
                    "auto_discovered": True,
                },
            }

            # Apply global configurations
            project_data = self._apply_global_configurations(project_data)

            return Project(project_data)

        except Exception as e:
            self.logger.error(f"Error creando proyecto desde {s7p_file_path}: {e}")
            return None

    def _create_manual_project(self, obs_dir: Dict[str, Any]) -> Optional[Project]:
        """Crear un proyecto para directorio manual"""
        try:
            dir_path = Path(obs_dir["path"])

            # Generar ID único
            project_id = self._generate_project_id(str(dir_path))

            # Usar nombre del directorio como nombre del proyecto
            project_name = dir_path.name

            project_data = {
                "id": project_id,
                "name": project_name,
                "path": str(dir_path),
                "type": "manual",
                "s7p_file": "",
                "observation_directory": str(dir_path.parent),
                "relative_path": dir_path.name,
                "backup_path": dir_path.name,
                "schedule_config": {
                    "schedule": self.config.global_settings.get(
                        "default_schedule", "daily"
                    ),
                    "schedule_time": self.config.global_settings.get(
                        "default_schedule_time", "02:00"
                    ),
                    "enabled": True,
                    "next_scheduled_backup": "",
                },
                "backup_history": {
                    "last_backup_date": "",
                    "last_backup_file": "",
                    "backup_count": 0,
                    "last_successful_backup": "",
                },
                "hash_info": {
                    "last_s7p_hash": "",
                    "last_full_hash": "",
                    "last_s7p_timestamp": "",
                    "last_s7p_size": 0,
                    "last_scan_timestamp": "",
                    "file_count": 0,
                    "total_size_bytes": 0,
                },
                "status": {
                    "current_status": "ready",
                    "last_error": None,
                    "retry_count": 0,
                    "next_retry": None,
                    "files_in_use": False,
                    "exclusivity_check_passed": True,
                    "last_status_update": datetime.now(timezone.utc).isoformat(),
                },
                "discovery_info": {
                    "discovered_date": datetime.now(timezone.utc).isoformat(),
                    "discovery_method": "manual_directory",
                    "auto_discovered": True,
                },
            }

            # Apply global configurations
            project_data = self._apply_global_configurations(project_data)

            return Project(project_data)

        except Exception as e:
            self.logger.error(
                f"Error creando proyecto manual para {obs_dir['path']}: {e}"
            )
            return None

    def _apply_global_configurations(
        self, project_data: Dict[str, Any]
    ) -> Dict[str, Any]:
        """Apply all global configurations to a new project"""
        global_settings = self.config.global_settings
        backup_options = self.config.backup_options

        # Apply global settings to schedule_config
        if "schedule_config" not in project_data:
            project_data["schedule_config"] = {}

        project_data["schedule_config"].update(
            {
                "schedule": global_settings.get("default_schedule", "daily"),
                "schedule_time": global_settings.get("default_schedule_time", "02:00"),
                "retry_delay_hours": global_settings.get("retry_delay_hours", 1),
                "backup_timeout_minutes": global_settings.get(
                    "backup_timeout_minutes", 0
                ),
                "min_backup_interval_minutes": global_settings.get(
                    "min_backup_interval_minutes", 10
                ),
                "min_free_space_mb": global_settings.get("min_free_space_mb", 100),
            }
        )

        # Apply backup options
        project_data["backup_options"] = {
            "compression_level": backup_options.get("compression_level", 6),
            "include_subdirectories": backup_options.get(
                "include_subdirectories", True
            ),
            "preserve_directory_structure": backup_options.get(
                "preserve_directory_structure", True
            ),
            "hash_algorithm": backup_options.get("hash_algorithm", "md5"),
            "hash_includes": backup_options.get("hash_includes", ["timestamp", "size"]),
            "backup_type": backup_options.get("backup_type", "complete"),
            "process_priority": backup_options.get("process_priority", "low"),
            "sequential_execution": backup_options.get("sequential_execution", True),
            "filename_format": backup_options.get(
                "filename_format", "HH-MM-SS_projects.zip"
            ),
            "date_format": backup_options.get("date_format", "YYYY-MM-DD"),
        }

        return project_data

    def _generate_project_id(self, path: str) -> str:
        """Generar ID único para un proyecto basado en su ruta"""
        # Usar hash de la ruta + timestamp para garantizar unicidad
        import hashlib

        path_hash = hashlib.md5(path.encode()).hexdigest()[:8]
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        return f"project_{path_hash}_{timestamp}"

    def _update_project_manager(self, discovered_projects: List[Project]) -> None:
        """Actualizar el project manager con proyectos descubiertos"""
        try:
            # Obtener proyectos existentes
            existing_projects = {
                p.path: p for p in self.project_manager.get_all_projects()
            }

            # Agregar nuevos proyectos
            new_projects_count = 0
            for project in discovered_projects:
                if project.path not in existing_projects:
                    self.project_manager.add_project(project.to_dict())
                    new_projects_count += 1
                    self.logger.debug(f"Proyecto agregado: {project.name}")

            if new_projects_count > 0:
                self.logger.info(f"Se agregaron {new_projects_count} nuevos proyectos")
            else:
                self.logger.info("No se encontraron nuevos proyectos")

            # Actualizar estadísticas
            self.project_manager.update_statistics()

        except Exception as e:
            self.logger.error(f"Error actualizando project manager: {e}")

    def rescan_observation_directories(self) -> int:
        """
        Re-escanear directorios de observación para nuevos proyectos
        Retorna el número de nuevos proyectos encontrados
        """
        self.logger.info("Iniciando re-escaneo de directorios de observación...")

        initial_count = len(self.project_manager.get_all_projects())

        # Re-descubrir proyectos
        self.discover_all_projects()

        final_count = len(self.project_manager.get_all_projects())
        new_projects = final_count - initial_count

        self.logger.info(
            f"Re-escaneo completado: {new_projects} nuevos proyectos encontrados"
        )

        return new_projects

    def check_everything_api_status(self) -> Dict[str, Any]:
        """
        Check the Everything API status without using fallback
        Returns detailed status information
        """
        status = {
            "enabled_in_config": False,
            "dll_path": None,
            "dll_exists": False,
            "searcher_initialized": False,
            "everything_available": False,
            "everything_diagnosis": None,
            "error_message": None,
        }

        try:
            # Check if Everything API is enabled in config
            status["enabled_in_config"] = self.config.everything_api.get(
                "enabled", False
            )

            # Get DLL path
            dll_path = self.config.get_dll_path()
            status["dll_path"] = dll_path

            # Check if DLL file exists
            if dll_path:
                from pathlib import Path

                status["dll_exists"] = Path(dll_path).exists()

            # Check if searcher was initialized
            status["searcher_initialized"] = self.everything_searcher is not None

            # Check if Everything is actually available
            if self.everything_searcher:
                status["everything_available"] = (
                    self.everything_searcher.is_everything_available()
                )

                # Get detailed diagnosis
                if hasattr(self.everything_searcher, "get_everything_diagnosis"):
                    status["everything_diagnosis"] = (
                        self.everything_searcher.get_everything_diagnosis()
                    )
            else:
                status["error_message"] = "Everything searcher not initialized"

        except Exception as e:
            status["error_message"] = f"Error checking Everything API status: {str(e)}"
            self.logger.error(f"Error in check_everything_api_status: {e}")

        return status

    def test_everything_api_search(self, test_query: str = "*.s7p") -> Dict[str, Any]:
        """
        Test Everything API search functionality without fallback
        Returns test results
        """
        test_result = {
            "test_query": test_query,
            "success": False,
            "results_count": 0,
            "results": [],
            "error_message": None,
            "execution_time_ms": 0,
        }

        try:
            if not self.everything_searcher:
                test_result["error_message"] = "Everything searcher not initialized"
                return test_result

            if not self.everything_searcher.is_everything_available():
                test_result["error_message"] = "Everything API not available"
                return test_result

            import time

            start_time = time.time()

            # Perform test search
            if test_query == "*.s7p":
                # Search for S7P files in observation directories
                obs_dirs = [
                    obs_dir["path"]
                    for obs_dir in self.config.observation_directories
                    if obs_dir.get("enabled", True)
                ]
                if obs_dirs:
                    results = self.everything_searcher.search_s7p_files(obs_dirs)
                else:
                    results = []
            else:
                # Custom search query - would need to implement this method
                test_result["error_message"] = (
                    "Custom search queries not implemented yet"
                )
                return test_result

            end_time = time.time()
            test_result["execution_time_ms"] = int((end_time - start_time) * 1000)
            test_result["success"] = True
            test_result["results_count"] = len(results)
            # Return only first 10 results
            test_result["results"] = results[:10]

        except Exception as e:
            test_result["error_message"] = f"Error during test search: {str(e)}"
            self.logger.error(f"Error in test_everything_api_search: {e}")

        return test_result

    def discover_s7_projects_everything_only(
        self, obs_dir: Dict[str, Any]
    ) -> List[Project]:
        """
        Discover S7 projects using ONLY Everything API - no fallback
        Returns empty list if Everything API is not available
        """
        projects = []
        dir_path = obs_dir["path"]

        try:
            # Check if Everything API is configured and available
            use_everything = self.config.everything_api.get("enabled", False)

            if not use_everything:
                self.logger.warning(
                    f"❌ Everything API disabled in config for {dir_path}"
                )
                return projects

            if not self.everything_searcher:
                self.logger.error(
                    f"❌ Everything searcher not initialized for {dir_path}"
                )
                return projects

            if not self.everything_searcher.is_everything_available():
                self.logger.error(f"❌ Everything API not available for {dir_path}")
                return projects

            # Use Everything API exclusively
            self.logger.info(f"🔍 Using Everything API ONLY for {dir_path}")

            s7p_files = self.everything_searcher.search_s7p_files([dir_path])

            if s7p_files:
                self.logger.info(f"✅ Everything API found {len(s7p_files)} files")
            else:
                self.logger.warning(
                    f"⚠️  Everything API found no .s7p files in {dir_path}"
                )

            self.logger.debug(f"Found {len(s7p_files)} .s7p files in {dir_path}")

            for s7p_file in s7p_files:
                project = self._create_project_from_s7p(s7p_file, obs_dir)
                if project:
                    projects.append(project)
                    self.logger.debug(f"Project created: {project.name}")

        except Exception as e:
            self.logger.error(f"❌ Error with Everything API in {dir_path}: {e}")
            # NO FALLBACK - return empty list

        return projects
