"""
Hash Utilities
Sistema de hash en dos etapas para optimizar detección de cambios
"""

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


class HashCalculator:
    """Calculadora de hash optimizada para el sistema de dos etapas"""
    
    def __init__(self, algorithm: str = "md5"):
        self.algorithm = algorithm.lower()
        self.logger = logging.getLogger(__name__)
    
    def calculate_file_hash(self, file_path: str) -> str:
        """
        Calcular hash de un archivo basado en timestamp + tamaño
        No lee el contenido del archivo para mayor velocidad
        """
        try:
            path = Path(file_path)
            if not path.exists():
                return ""
            
            stat = path.stat()
            timestamp = str(stat.st_mtime)
            size = str(stat.st_size)
            
            # Crear hash combinando timestamp y tamaño
            hash_input = f"{timestamp}:{size}".encode('utf-8')
            
            if self.algorithm == "md5":
                return hashlib.md5(hash_input).hexdigest()
            elif self.algorithm == "sha256":
                return hashlib.sha256(hash_input).hexdigest()
            else:
                raise ValueError(f"Algoritmo de hash no soportado: {self.algorithm}")
                
        except Exception as e:
            self.logger.error(f"Error calculando hash para {file_path}: {e}")
            return ""
    
    def calculate_s7p_hash(self, s7p_file_path: str) -> Tuple[str, str, int]:
        """
        Calcular hash específico para archivo .s7p
        Retorna: (hash, timestamp_iso, size_bytes)
        """
        try:
            path = Path(s7p_file_path)
            if not path.exists():
                return "", "", 0
            
            stat = path.stat()
            timestamp = str(stat.st_mtime)
            size = stat.st_size
            
            # Crear hash
            hash_input = f"{timestamp}:{size}".encode('utf-8')
            if self.algorithm == "md5":
                file_hash = hashlib.md5(hash_input).hexdigest()
            else:
                file_hash = hashlib.sha256(hash_input).hexdigest()
            
            # Convertir timestamp a ISO format
            from datetime import datetime
            timestamp_iso = datetime.fromtimestamp(stat.st_mtime).isoformat()
            
            return file_hash, timestamp_iso, size
            
        except Exception as e:
            self.logger.error(f"Error calculando hash S7P para {s7p_file_path}: {e}")
            return "", "", 0
    
    def calculate_directory_hash(self, directory_path: str, exclude_patterns: List[str] = None) -> Tuple[str, int, int]:
        """
        Calcular hash de todos los archivos en un directorio
        Retorna: (hash_completo, cantidad_archivos, tamaño_total)
        """
        try:
            path = Path(directory_path)
            if not path.exists():
                return "", 0, 0
            
            if exclude_patterns is None:
                exclude_patterns = []
            
            file_hashes = []
            file_count = 0
            total_size = 0
            
            # Recorrer todos los archivos recursivamente
            for file_path in path.rglob("*"):
                if file_path.is_file():
                    # Verificar si el archivo debe ser excluido
                    should_exclude = False
                    for pattern in exclude_patterns:
                        if pattern in str(file_path):
                            should_exclude = True
                            break
                    
                    if should_exclude:
                        continue
                    
                    try:
                        stat = file_path.stat()
                        file_hash = self.calculate_file_hash(str(file_path))
                        if file_hash:
                            # Incluir la ruta relativa en el hash para detectar movimientos
                            relative_path = file_path.relative_to(path)
                            combined_hash_input = f"{relative_path}:{file_hash}".encode('utf-8')
                            
                            if self.algorithm == "md5":
                                combined_hash = hashlib.md5(combined_hash_input).hexdigest()
                            else:
                                combined_hash = hashlib.sha256(combined_hash_input).hexdigest()
                            
                            file_hashes.append(combined_hash)
                            file_count += 1
                            total_size += stat.st_size
                    except Exception as e:
                        self.logger.warning(f"Error procesando archivo {file_path}: {e}")
                        continue
            
            # Crear hash final combinando todos los hashes de archivos
            if file_hashes:
                # Ordenar para asegurar consistencia
                file_hashes.sort()
                combined_input = ":".join(file_hashes).encode('utf-8')
                
                if self.algorithm == "md5":
                    final_hash = hashlib.md5(combined_input).hexdigest()
                else:
                    final_hash = hashlib.sha256(combined_input).hexdigest()
                
                return final_hash, file_count, total_size
            else:
                return "", 0, 0
                
        except Exception as e:
            self.logger.error(f"Error calculando hash de directorio {directory_path}: {e}")
            return "", 0, 0
    
    def compare_hashes(self, hash1: str, hash2: str) -> bool:
        """Comparar dos hashes de forma segura"""
        if not hash1 or not hash2:
            return False
        return hash1 == hash2
    
    def has_s7p_changed(self, s7p_file_path: str, last_known_hash: str, 
                       last_known_timestamp: str, last_known_size: int) -> bool:
        """
        Verificar si el archivo .s7p ha cambiado (Etapa 1 del sistema de hash)
        """
        try:
            current_hash, current_timestamp, current_size = self.calculate_s7p_hash(s7p_file_path)
            
            if not current_hash:
                return True  # Si no se puede calcular, asumir que cambió
            
            # Comparar hash actual con el último conocido
            if not last_known_hash:
                return True  # Si no hay hash previo, definitivamente cambió
            
            return not self.compare_hashes(current_hash, last_known_hash)
            
        except Exception as e:
            self.logger.error(f"Error verificando cambios en S7P {s7p_file_path}: {e}")
            return True  # En caso de error, asumir que cambió
    
    def has_directory_changed(self, directory_path: str, last_known_hash: str,
                            exclude_patterns: List[str] = None) -> Tuple[bool, str, int, int]:
        """
        Verificar si el directorio ha cambiado (Etapa 2 del sistema de hash)
        Retorna: (ha_cambiado, nuevo_hash, cantidad_archivos, tamaño_total)
        """
        try:
            current_hash, file_count, total_size = self.calculate_directory_hash(
                directory_path, exclude_patterns
            )
            
            if not current_hash:
                return True, "", 0, 0
            
            if not last_known_hash:
                return True, current_hash, file_count, total_size
            
            has_changed = not self.compare_hashes(current_hash, last_known_hash)
            return has_changed, current_hash, file_count, total_size
            
        except Exception as e:
            self.logger.error(f"Error verificando cambios en directorio {directory_path}: {e}")
            return True, "", 0, 0
