#!/usr/bin/env python3
"""
Everything3 Working Wrapper
Wrapper que usa solo las funciones disponibles en Everything3 SDK
"""

import ctypes
import os
import logging
from typing import List, Optional
from pathlib import Path

logger = logging.getLogger(__name__)


class Everything3WorkingSearcher:
    """
    Wrapper para Everything SDK 3 usando solo funciones disponibles
    """

    def __init__(self, dll_path: Optional[str] = None):
        self.dll_path = dll_path
        self.dll = None
        self.client = None
        self._is_initialized = False

    def _load_dll(self) -> bool:
        """Cargar la DLL de Everything SDK 3"""
        try:
            if not self.dll_path or not os.path.exists(self.dll_path):
                logger.error(f"Everything3.dll no encontrada: {self.dll_path}")
                return False

            self.dll = ctypes.WinDLL(self.dll_path)

            # Configurar funciones disponibles
            self._setup_functions()

            logger.info(f"Everything3.dll cargada: {self.dll_path}")
            return True

        except Exception as e:
            logger.error(f"Error cargando Everything3.dll: {e}")
            return False

    def _setup_functions(self):
        """Configurar las funciones disponibles en la DLL"""

        # Everything3_ConnectW - Conectar a instancia
        self.dll.Everything3_ConnectW.argtypes = [ctypes.c_wchar_p]
        self.dll.Everything3_ConnectW.restype = ctypes.c_void_p

        # Everything3_IsDBLoaded - Verificar si la DB está cargada
        self.dll.Everything3_IsDBLoaded.argtypes = [ctypes.c_void_p]
        self.dll.Everything3_IsDBLoaded.restype = ctypes.c_bool

        # Everything3_Search - Realizar búsqueda
        self.dll.Everything3_Search.argtypes = [ctypes.c_void_p, ctypes.c_wchar_p]
        self.dll.Everything3_Search.restype = ctypes.c_bool

        # Everything3_GetResults - Obtener número de resultados
        self.dll.Everything3_GetResults.argtypes = [ctypes.c_void_p]
        self.dll.Everything3_GetResults.restype = ctypes.c_uint32

        # Everything3_GetResultFullPathNameW - Obtener ruta completa
        self.dll.Everything3_GetResultFullPathNameW.argtypes = [
            ctypes.c_void_p,  # client
            ctypes.c_uint32,  # index
            ctypes.c_wchar_p,  # buf
            ctypes.c_uint32,  # buf_size
        ]
        self.dll.Everything3_GetResultFullPathNameW.restype = ctypes.c_uint32

        # Everything3_GetLastError - Obtener último error
        self.dll.Everything3_GetLastError.argtypes = [ctypes.c_void_p]
        self.dll.Everything3_GetLastError.restype = ctypes.c_uint32

    def initialize(self) -> bool:
        """Inicializar Everything API"""
        if self._is_initialized:
            return True

        try:
            # Cargar DLL
            if not self._load_dll():
                return False

            # Conectar a Everything
            if not self._connect_to_everything():
                return False

            self._is_initialized = True
            logger.info("✅ Everything3 inicializado correctamente")
            return True

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

    def _connect_to_everything(self) -> bool:
        """Conectar a la instancia de Everything"""
        try:
            # Conectar a Everything 1.5a
            self.client = self.dll.Everything3_ConnectW("1.5a")

            if self.client and self.client != 0:
                # Verificar que la DB esté cargada
                if self.dll.Everything3_IsDBLoaded(self.client):
                    logger.info("✅ Conectado a Everything con DB cargada")
                    return True
                else:
                    logger.warning("⚠️  Conectado pero DB no cargada")
                    return True  # Continuar, la DB puede cargarse después
            else:
                error_code = self.dll.Everything3_GetLastError(0)
                logger.error(f"❌ Falló conexión, error: {error_code}")
                return False

        except Exception as e:
            logger.error(f"❌ Error conectando: {e}")
            return False

    def is_everything_available(self) -> bool:
        """Verificar si Everything está disponible"""
        return self._is_initialized and self.client is not None

    def search_s7p_files(self, search_directories: List[str]) -> List[str]:
        """Buscar archivos .s7p en los directorios especificados"""
        if not self.is_everything_available():
            logger.error("Everything3 no está disponible")
            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
            unique_files = list(set(all_s7p_files))
            logger.info(f"Encontrados {len(unique_files)} archivos .s7p únicos")
            return unique_files

        except Exception as e:
            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)

            # Construir consulta para Everything3 - usar sintaxis más simple
            query = f'ext:s7p folder:"{directory}"'

            logger.debug(f"Buscando con consulta: {query}")

            # Ejecutar búsqueda
            search_success = self.dll.Everything3_Search(self.client, query)

            if not search_success:
                error_code = self.dll.Everything3_GetLastError(self.client)
                logger.warning(f"Búsqueda falló, error: {error_code}")

                # Intentar con sintaxis alternativa
                query_alt = f'*.s7p path:"{directory}"'
                logger.debug(f"Intentando consulta alternativa: {query_alt}")
                search_success = self.dll.Everything3_Search(self.client, query_alt)

                if not search_success:
                    error_code = self.dll.Everything3_GetLastError(self.client)
                    logger.error(f"Ambas búsquedas fallaron, error: {error_code}")
                    return []

            # Obtener número de resultados
            num_results = self.dll.Everything3_GetResults(self.client)
            logger.debug(f"Encontrados {num_results} resultados")

            if num_results == 0:
                return []

            # Obtener rutas de los resultados
            results = []
            for i in range(min(num_results, 1000)):  # Limitar a 1000 resultados
                try:
                    path = self._get_result_path(i)
                    if path:
                        results.append(path)
                        logger.debug(f"Resultado {i}: {path}")
                    else:
                        logger.warning(f"Resultado {i} sin ruta válida")
                except Exception as e:
                    logger.error(f"Error obteniendo resultado {i}: {e}")
                    continue

            logger.info(f"Total resultados válidos: {len(results)}")
            return results

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

    def _get_result_path(self, index: int) -> Optional[str]:
        """Obtener la ruta completa de un resultado por índice"""
        try:
            # Buffer para la ruta (tamaño máximo de Windows PATH)
            buf_size = 4096  # Reducir tamaño del buffer
            buf = ctypes.create_unicode_buffer(buf_size)

            # Obtener la ruta completa
            chars_copied = self.dll.Everything3_GetResultFullPathNameW(
                self.client, ctypes.c_uint32(index), buf, ctypes.c_uint32(buf_size)
            )

            if chars_copied > 0 and chars_copied < buf_size:
                result_path = buf.value
                if result_path and len(result_path) > 0:
                    return result_path
                else:
                    logger.warning(f"Ruta vacía para índice {index}")
                    return None
            else:
                logger.warning(
                    f"No se pudo obtener ruta para índice {index}, chars: {chars_copied}"
                )
                return None

        except Exception as e:
            logger.error(f"Error obteniendo ruta para índice {index}: {e}")
            return None

    def get_everything_diagnosis(self) -> dict:
        """Obtener diagnóstico del estado de Everything"""
        diagnosis = {
            "dll_path": self.dll_path,
            "dll_exists": os.path.exists(self.dll_path) if self.dll_path else False,
            "dll_loaded": self.dll is not None,
            "client_connected": self.client is not None,
            "is_initialized": self._is_initialized,
            "everything_available": self.is_everything_available(),
        }

        # Información adicional si está conectado
        if self.client:
            try:
                diagnosis["db_loaded"] = self.dll.Everything3_IsDBLoaded(self.client)
            except Exception as e:
                diagnosis["db_loaded_error"] = str(e)

        return diagnosis


def create_everything3_working_searcher(
    dll_path: str,
) -> Optional[Everything3WorkingSearcher]:
    """Crear una instancia del searcher Everything3 funcional"""
    try:
        searcher = Everything3WorkingSearcher(dll_path)
        if searcher.initialize():
            return searcher
        else:
            logger.warning("No se pudo inicializar Everything3 searcher")
            return None
    except Exception as e:
        logger.error(f"Error creando Everything3 searcher: {e}")
        return None
