"""
Everything API Wrapper
Integración con Everything para búsqueda rápida de archivos .s7p
"""

import os
import ctypes
from pathlib import Path
from typing import List, Optional, Tuple, Dict, Any
import logging


class EverythingSearcher:
    """Wrapper para la API de Everything"""

    def __init__(self, dll_path: str = None):
        self.logger = logging.getLogger(__name__)
        self.dll = None
        self.is_initialized = False

        if dll_path:
            self.load_dll(dll_path)

    def load_dll(self, dll_path: str) -> bool:
        """Cargar la DLL de Everything"""
        try:
            if not os.path.exists(dll_path):
                self.logger.error(f"DLL de Everything no encontrada: {dll_path}")
                return False

            self.dll = ctypes.WinDLL(dll_path)

            # Definir funciones de la API
            self._setup_api_functions()

            # Inicializar Everything
            self.is_initialized = self._initialize_everything()

            if self.is_initialized:
                self.logger.info("Everything API inicializada correctamente")
            else:
                self.logger.error("Error inicializando Everything API")

            return self.is_initialized

        except Exception as e:
            self.logger.error(f"Error cargando DLL de Everything: {e}")
            return False

    def _setup_api_functions(self):
        """Configurar las funciones de la API"""
        try:
            # Everything_SetSearchW - Establecer consulta de búsqueda
            self.dll.Everything_SetSearchW.argtypes = [ctypes.c_wchar_p]
            self.dll.Everything_SetSearchW.restype = None

            # Everything_SetMatchPath - Habilitar coincidencia de ruta completa
            self.dll.Everything_SetMatchPath.argtypes = [ctypes.c_bool]
            self.dll.Everything_SetMatchPath.restype = None

            # Everything_SetMatchCase - Habilitar coincidencia de mayúsculas/minúsculas
            self.dll.Everything_SetMatchCase.argtypes = [ctypes.c_bool]
            self.dll.Everything_SetMatchCase.restype = None

            # Everything_QueryW - Ejecutar consulta
            self.dll.Everything_QueryW.argtypes = [ctypes.c_bool]
            self.dll.Everything_QueryW.restype = ctypes.c_bool

            # Everything_GetNumResults - Obtener número de resultados
            self.dll.Everything_GetNumResults.restype = ctypes.c_uint

            # Everything_GetResultFullPathNameW - Obtener ruta completa del resultado
            self.dll.Everything_GetResultFullPathNameW.argtypes = [
                ctypes.c_uint,
                ctypes.c_wchar_p,
                ctypes.c_uint,
            ]
            self.dll.Everything_GetResultFullPathNameW.restype = ctypes.c_uint

            # Everything_GetLastError - Obtener último error
            self.dll.Everything_GetLastError.restype = ctypes.c_uint

            # Everything_IsDBLoaded - Verificar si la DB está cargada
            self.dll.Everything_IsDBLoaded.restype = ctypes.c_bool

            # Everything_IsAdmin - Verificar permisos de administrador
            self.dll.Everything_IsAdmin.restype = ctypes.c_bool

            # Everything_SetRequestFlags - Configurar flags de request
            self.dll.Everything_SetRequestFlags.argtypes = [ctypes.c_uint]
            self.dll.Everything_SetRequestFlags.restype = None

            self.logger.debug("Funciones API configuradas correctamente")

        except Exception as e:
            self.logger.error(f"Error configurando funciones API: {e}")
            raise

    def _initialize_everything(self) -> bool:
        """Inicializar Everything con configuraciones por defecto"""
        try:
            # Verificar si Everything DB está cargada
            db_loaded = False
            if hasattr(self.dll, "Everything_IsDBLoaded"):
                db_loaded = self.dll.Everything_IsDBLoaded()
                self.logger.debug(f"Everything DB loaded: {db_loaded}")
                if not db_loaded:
                    self.logger.warning(
                        "Everything database no está cargada - continuando"
                    )

            # Verificar permisos de administrador
            if hasattr(self.dll, "Everything_IsAdmin"):
                is_admin = self.dll.Everything_IsAdmin()
                self.logger.debug(f"Everything admin privileges: {is_admin}")

            # Configurar request flags para obtener rutas completas
            if hasattr(self.dll, "Everything_SetRequestFlags"):
                EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME = 0x00000004

                self.dll.Everything_SetRequestFlags(
                    EVERYTHING_REQUEST_FULL_PATH_AND_FILE_NAME
                )

            # Configurar opciones de búsqueda
            self.dll.Everything_SetMatchPath(True)
            self.dll.Everything_SetMatchCase(False)

            # Test simple para verificar IPC (más permisivo)
            self.dll.Everything_SetSearchW("test")
            test_result = self.dll.Everything_QueryW(True)
            test_error = self.dll.Everything_GetLastError()

            if test_error == 2:  # EVERYTHING_ERROR_IPC
                self.logger.error("Error IPC - Everything service no disponible")
                return False
            elif test_error == 1:  # EVERYTHING_ERROR_MEMORY
                self.logger.warning("Warning: Everything memory error")
            elif test_error != 0:
                self.logger.warning(f"Warning en test IPC: error {test_error}")

            # Si DB no está cargada pero IPC funciona, aún podemos continuar
            if not db_loaded and test_error == 0:
                self.logger.info("Everything IPC OK, database loading...")

            self.logger.debug("Everything inicializado correctamente")
            return True

        except Exception as e:
            self.logger.error(f"Error inicializando Everything: {e}")
            return False

    def search_s7p_files(self, search_directories: List[str]) -> List[str]:
        """
        Buscar archivos .s7p en los directorios especificados
        Retorna lista de rutas completas a archivos .s7p encontrados
        """
        if not self.is_initialized:
            self.logger.error("Everything no está inicializado")
            return []

        try:
            all_s7p_files = []

            for directory in search_directories:
                s7p_files = self._search_s7p_in_directory(directory)
                all_s7p_files.extend(s7p_files)

            # Eliminar duplicados manteniendo el orden
            unique_files = []
            seen = set()
            for file_path in all_s7p_files:
                if file_path not in seen:
                    seen.add(file_path)
                    unique_files.append(file_path)

            self.logger.info(f"Encontrados {len(unique_files)} archivos .s7p únicos")
            return unique_files

        except Exception as e:
            self.logger.error(f"Error buscando archivos S7P: {e}")
            return []

    def _search_s7p_in_directory(self, directory: str) -> List[str]:
        """Buscar archivos .s7p en un directorio específico"""
        try:
            # Normalizar la ruta del directorio
            directory = os.path.normpath(directory)

            # Estrategias de búsqueda diferentes para probar
            search_strategies = [
                "*.s7p",  # Búsqueda global simple
                "ext:s7p",  # Solo por extensión
                f'*.s7p path:"{directory}"',  # Path con comillas
                f'*.s7p folder:"{directory}"',  # Usando folder
            ]

            for i, search_query in enumerate(search_strategies):
                self.logger.debug(f"Probando estrategia {i+1}: {search_query}")

                # Establecer consulta
                self.dll.Everything_SetSearchW(search_query)

                # Ejecutar búsqueda
                if not self.dll.Everything_QueryW(True):
                    error_code = self.dll.Everything_GetLastError()
                    self.logger.debug(f"Estrategia {i+1} falló: código {error_code}")
                    continue

                # Obtener número de resultados
                num_results = self.dll.Everything_GetNumResults()
                self.logger.debug(f"Estrategia {i+1} encontró {num_results} resultados")

                if num_results == 0:
                    continue

                # Filtrar resultados por directorio si no usamos path en query
                results = []
                buffer_size = 4096

                for j in range(num_results):
                    path_buffer = ctypes.create_unicode_buffer(buffer_size)
                    chars_copied = self.dll.Everything_GetResultFullPathNameW(
                        j, path_buffer, buffer_size
                    )

                    if chars_copied > 0:
                        file_path = path_buffer.value
                        if file_path and os.path.exists(file_path):
                            # Filtrar por directorio si es búsqueda global
                            if i == 0 or i == 1:  # Búsquedas globales
                                if file_path.startswith(directory):
                                    results.append(file_path)
                                    self.logger.debug(
                                        f"Archivo S7P válido: {file_path}"
                                    )
                            else:  # Búsquedas con path específico
                                results.append(file_path)
                                self.logger.debug(
                                    f"Archivo S7P encontrado: {file_path}"
                                )

                if results:
                    self.logger.info(
                        f"Estrategia {i+1} exitosa: {len(results)} archivos"
                    )
                    return results

            # Si llegamos aquí, ninguna estrategia funcionó
            self.logger.warning(f"Ninguna estrategia funcionó para {directory}")
            return []

        except Exception as e:
            self.logger.error(f"Error buscando en directorio {directory}: {e}")
            return []

    def search_files_by_pattern(
        self, pattern: str, max_results: int = 1000
    ) -> List[str]:
        """
        Buscar archivos usando un patrón específico
        """
        if not self.is_initialized:
            self.logger.error("Everything no está inicializado")
            return []

        try:
            self.logger.debug(f"Búsqueda por patrón: {pattern}")

            # Establecer consulta
            self.dll.Everything_SetSearchW(pattern)

            # Ejecutar búsqueda
            if not self.dll.Everything_QueryW(True):
                error_code = self.dll.Everything_GetLastError()
                self.logger.error(f"Error en consulta Everything: código {error_code}")
                return []

            # Obtener número de resultados
            num_results = self.dll.Everything_GetNumResults()
            actual_results = min(num_results, max_results)

            self.logger.debug(
                f"Procesando {actual_results} de {num_results} resultados"
            )

            results = []
            buffer_size = 4096

            for i in range(actual_results):
                path_buffer = ctypes.create_unicode_buffer(buffer_size)
                chars_copied = self.dll.Everything_GetResultFullPathNameW(
                    i, path_buffer, buffer_size
                )

                if chars_copied > 0:
                    file_path = path_buffer.value
                    if file_path and os.path.exists(file_path):
                        results.append(file_path)

            return results

        except Exception as e:
            self.logger.error(f"Error buscando patrón {pattern}: {e}")
            return []

    def diagnose(self) -> Dict[str, Any]:
        """Diagnosticar el estado de Everything API"""
        diagnosis = {
            "dll_loaded": bool(self.dll),
            "is_initialized": self.is_initialized,
            "db_loaded": False,
            "is_admin": False,
            "ipc_working": False,
            "test_query_result": None,
            "error_code": None,
        }

        if not self.dll or not self.is_initialized:
            return diagnosis

        try:
            # Verificar DB
            if hasattr(self.dll, "Everything_IsDBLoaded"):
                diagnosis["db_loaded"] = self.dll.Everything_IsDBLoaded()

            # Verificar admin
            if hasattr(self.dll, "Everything_IsAdmin"):
                diagnosis["is_admin"] = self.dll.Everything_IsAdmin()

            # Test IPC
            self.dll.Everything_SetSearchW("*.s7p")
            test_result = self.dll.Everything_QueryW(True)
            error_code = self.dll.Everything_GetLastError()

            diagnosis["test_query_result"] = test_result
            diagnosis["error_code"] = error_code
            diagnosis["ipc_working"] = error_code == 0

            if test_result:
                diagnosis["test_results_count"] = self.dll.Everything_GetNumResults()

        except Exception as e:
            diagnosis["exception"] = str(e)

        return diagnosis

    def is_everything_available(self) -> bool:
        """Verificar si Everything está disponible y funcionando"""
        if not self.is_initialized:
            return False

        # Verificar IPC con test simple
        try:
            self.dll.Everything_SetSearchW("test")
            self.dll.Everything_QueryW(True)
            error_code = self.dll.Everything_GetLastError()
            return error_code == 0  # Only return True if no errors
        except:
            return False

    def get_project_directory_from_s7p(
        self, s7p_file_path: str, observation_directory: str
    ) -> Tuple[str, str]:
        """
        Determinar el directorio del proyecto y la ruta relativa desde un archivo .s7p
        Retorna: (directorio_proyecto, ruta_relativa)
        """
        try:
            s7p_path = Path(s7p_file_path)
            obs_path = Path(observation_directory)

            # El directorio del proyecto es el padre del archivo .s7p
            project_dir = s7p_path.parent

            # Calcular ruta relativa desde el directorio de observación
            try:
                relative_path = project_dir.relative_to(obs_path)
                return str(project_dir), str(relative_path)
            except ValueError:
                # Si no se puede calcular la ruta relativa, usar ruta absoluta
                self.logger.warning(
                    f"No se puede calcular ruta relativa para {s7p_file_path}"
                )
                return str(project_dir), str(project_dir)

        except Exception as e:
            self.logger.error(
                f"Error determinando directorio de proyecto para {s7p_file_path}: {e}"
            )
            return "", ""


# Funciones de utilidad para crear instancias
def create_everything_searcher(dll_path: str) -> Optional[EverythingSearcher]:
    """Crear una instancia de EverythingSearcher con manejo de errores"""
    try:
        searcher = EverythingSearcher(dll_path)
        if searcher.is_everything_available():
            return searcher
        else:
            return None
    except Exception as e:
        logging.getLogger(__name__).error(f"Error creando EverythingSearcher: {e}")
        return None
