import sys
import os
import threading
import hashlib
import math
import fuse
from module_utils import find_module_path, prepare_mountpoint, unmount_filesystem
import glob

# Ensure relevant paths are in sys.path
venv_site_packages = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "veny", "Lib", "site-packages"))
if venv_site_packages not in sys.path:
    sys.path.insert(0, venv_site_packages)

# Adjust paths dynamically for specific modules
for module in ["fusepy", "zstd", "numpy", "Pillow", "anytree"]:
    module_paths = glob.glob(os.path.join(venv_site_packages, f"*{module}*"))
    for module_path in module_paths:
        if module_path not in sys.path:
            sys.path.insert(0, module_path)

import errno
import pickle
import subprocess
from collections import defaultdict
from time import time
# Implementación ligera de Node y DictExporter para evitar dependencias externas durante las pruebas.
class Node:
    def __init__(self, name, parent=None):
        self.name = name
        self.parent = parent
        self.children = []
        if parent is not None:
            parent.children.append(self)
    def __repr__(self):
        return f"Node({self.name})"
    @staticmethod
    def from_dict(d):
        def build(node_dict):
            n = Node(node_dict.get('name'))
            for ch in node_dict.get('children', []):
                child = build(ch)
                child.parent = n
                n.children.append(child)
            return n
        return build(d)

class RenderTree:
    pass

class DictExporter:
    def export(self, node):
        def dump(n):
            return {'name': n.name, 'children': [dump(c) for c in getattr(n, 'children', [])]}
        return dump(node)

import concurrent.futures
# Import numpy de forma robusta (puede fallar en algunos entornos durante la carga inicial)
try:
    import numpy as np
except Exception:
    np = None

try:
    from PIL import Image
except ImportError:
    Image = None
import io

try:
    import zstd
except ImportError:
    print("zstd module not found. Ensure it is installed or provide a fallback.")
    zstd = None

fuse.fuse_python_api = (0, 2)

# QuantumFigure class: Represents a data block with quantum-like properties
class QuantumFigure:
    def __init__(self, shape, data, block_size=4096):
        self.shape = shape
        self.data = data
        self.block_size = block_size
        self.states = {}  # Superposition states (label -> processed data or representations)
        self.metadata = {}  # Extended metadata (length, sha256, entropy, etc.)
        self.entangled_figures = []  # Entangled figures
        # MPS representation stored separately (list of cores) for lossless tensor-train representation
        self.mps = None
        self.process_data()

    def process_data(self):
        """Process data to extract meaningful properties (length, entropy, hashes)."""
        data_length = len(self.data)
        radius = math.sqrt(data_length) if data_length > 0 else 0.0
        diameter = radius * 2
        density = data_length / self.block_size if self.block_size else 0.0
        # Shannon entropy of bytes
        entropy = 0.0
        if data_length > 0:
            from collections import Counter
            counts = Counter(self.data)
            p_sum = 0.0
            entropy = 0.0
            for v in counts.values():
                p = v / data_length
                entropy -= p * math.log2(p)
        overlap = 0.0
        sha = hashlib.sha256(self.data).hexdigest()
        self.metadata = {
            'length': data_length,
            'sha256': sha,
            'radius': radius,
            'diameter': diameter,
            'density': density,
            'entropy': entropy,
            'overlap': overlap
        }
        # store raw block data as default state (decompression happens at file reconstruction)
        self.states['default'] = self.data
        # generate lossless MPS/TT representation if possible
        try:
            from mps_utils import tt_decompose
            self.mps = tt_decompose(self.data)
            self.states['mps'] = True
        except Exception as e:
            # If MPS generation fails, keep raw data only
            self.mps = None
            self.states['mps'] = False
            print(f"WARNING: MPS generation failed for a block: {e}")

    def compress_data(self, data):
        """Compress data using zstd if available, otherwise zlib."""
        if data is None:
            return b""
        try:
            if zstd and hasattr(zstd, 'compress'):
                return zstd.compress(data, 22)
            import zlib
            return zlib.compress(data)
        except Exception as e:
            print(f"Compression failed: {e}; storing raw data")
            return data

    def decompress_data(self, compressed_data):
        """Decompress data using zstd or zlib."""
        try:
            if zstd and hasattr(zstd, 'decompress'):
                return zstd.decompress(compressed_data)
            import zlib
            return zlib.decompress(compressed_data)
        except Exception as e:
            print(f"Decompression failed: {e}; returning raw data")
            return compressed_data

    def entangle(self, other_figure):
        """Simulate quantum entanglement between figures."""
        if other_figure not in self.entangled_figures:
            self.entangled_figures.append(other_figure)
            other_figure.entangle(self)

    def collapse_state(self, measurement='default'):
        """Collapse to the requested state, prefer MPS reconstruction if available."""
        # If MPS available, reconstruct from it (lossless)
        if self.mps:
            try:
                from mps_utils import tt_reconstruct
                return tt_reconstruct(self.mps)
            except Exception:
                pass
        # Fallback to stored compressed/raw state
        val = self.states.get(measurement, None)
        if val is None:
            return None
        # If it's compressed bytes, try to decompress
        try:
            return self.decompress_data(val)
        except Exception:
            return val

    def update_state(self, state_label, new_data):
        """Update or add a new superposition state."""
        self.states[state_label] = new_data

    def __repr__(self):
        return f"QuantumFigure(shape={self.shape}, metadata={self.metadata})"

class DedupFS:
    def __init__(self, mountpoint, *args, enable_fuse=False, **kwargs):
        self.mountpoint = os.path.abspath(mountpoint)
        self.global_index_path = os.path.join(os.path.dirname(self.mountpoint), "global_index.pkl")
        self.files = {}
        self.data_blocks = {}
        self.block_size = 4096  # 4KB blocks
        self.merkle_tree = Node("root")
        self.snapshots = {}
        self.circular_positions = {}  # Store circular positions for blocks
        self.circular_metadata = {}  # Store additional metadata for circular encoding
        self.quantum_figures = {}  # Store QuantumFigure instances for each block
        self.fs_lock = threading.Lock()  # Agregar el bloqueo faltante.
        self.enable_fuse = enable_fuse
        self.mounted = False
        self.fuse_mounted = False

    def hash_block(self, data):
        return hashlib.sha256(data).hexdigest()

    def add_to_merkle_tree(self, block_hash):
        Node(block_hash, parent=self.merkle_tree)

    def circular_encode(self, data, index):
        """Convert data into circular coordinates."""
        radius = math.sqrt(len(data))  # Radius based on data length
        angle = (index * 137.5) % 360  # Pseudo-random distribution
        return (radius, angle)

    def advanced_circular_encode(self, data, index):
        """
        Advanced circular encoding with extended metadata.
        Returns radius, angle, diameter, density, and overlap.
        """
        radius = math.sqrt(len(data))
        angle = (index * 137.5 + radius) % 360  # Simulated advanced mapping
        diameter = radius * 2
        density = len(data) / self.block_size
        overlap = 0.0  # Placeholder for real overlap calculation
        return {
            'radius': radius,
            'angle': angle,
            'diameter': diameter,
            'density': density,
            'overlap': overlap
        }

    def getattr(self, path, fh=None):
        """Get attributes of a file or directory."""
        return dict(st_mode=(0o755 | 0o040000), st_nlink=2)

    def readdir(self, path, fh):
        """List files in a directory."""
        return ['.', '..'] + list(self.files.keys())

    def read(self, path, size, offset, fh):
        """
        Reconstruct the file using the collapsed state of each QuantumFigure,
        ensuring the original data is recovered.
        Compatible with both normal and compacted Global Index.
        """
        if path not in self.files:
            raise fuse.FuseOSError(errno.ENOENT)

        # Verificar si el índice global está compactado
        compacted_index = self.files.get("compacted_index")
        if compacted_index:
            # Reconstruir desde el índice compacto (buscar la figura global por hash)
            qhash = compacted_index.get('blocks', [None])[0]
            compacted_qf = self.quantum_figures.get(qhash)
            if compacted_qf is None:
                raise RuntimeError("Error: No se encontró la figura cuántica compactada en el índice global.")
            compacted_data = compacted_qf.collapse_state()
            if not compacted_data:
                raise RuntimeError("Error: No se pudo descomprimir el índice compacto.")

            # Reconstruir el archivo solicitado usando los hashes de bloque almacenados
            file_blocks = []
            for block_hash in self.files[path]['blocks']:
                qf = self.quantum_figures.get(block_hash)
                if qf is not None:
                    file_blocks.append(qf.collapse_state())
                else:
                    file_blocks.append(self.data_blocks.get(block_hash, b""))
            compressed_buf = b"".join(file_blocks)
            # Decompress according to the recorded compression method
            method = self.files[path].get('compression')
            if method == 'zstd' and zstd and hasattr(zstd, 'decompress'):
                try:
                    file_data = zstd.decompress(compressed_buf)
                except Exception:
                    file_data = compressed_buf
            elif method == 'zlib':
                import zlib
                try:
                    file_data = zlib.decompress(compressed_buf)
                except Exception:
                    file_data = compressed_buf
            elif method == 'lzma':
                import lzma
                try:
                    file_data = lzma.decompress(compressed_buf)
                except Exception:
                    file_data = compressed_buf
            else:
                # image_png or unknown: attempt to return raw compressed_buf
                file_data = compressed_buf
        else:
            # Reconstruir desde el índice normal
            file_blocks = []
            for block_hash in self.files[path]['blocks']:
                qf = self.quantum_figures.get(block_hash)
                if qf is not None:
                    file_blocks.append(qf.collapse_state())
                else:
                    file_blocks.append(self.data_blocks[block_hash])  # Fallback
            file_data = b"".join(file_blocks)

        return file_data[offset:offset + size]

    def choose_compression(self, path, buf):
        """
        Selecciona un método de compresión avanzado basado en la extensión del archivo.
        Devuelve una tupla (metodo, bytes_comprimidos).
        """
        ext = os.path.splitext(path)[1].lower()
        if ext == ".txt" and zstd:
            return ('zstd', zstd.compress(buf, 22))
        elif ext in [".png", ".webp"]:
            return ('image_png', self.lossless_image_compress(buf))
        elif ext in [".exe", ".dll"]:
            return ('lzma', self.lzx_compress(buf))
        else:
            if zstd:
                return ('zstd', zstd.compress(buf, 22))
            else:
                import zlib
                return ('zlib', zlib.compress(buf))

    def lzx_compress(self, buf):
        """
        Implementa compresión LZX utilizando una biblioteca externa o un comando del sistema.
        """
        try:
            import pylzx  # Ejemplo de biblioteca ficticia
            return pylzx.compress(buf)
        except ImportError:
            print("LZX compression not available. Falling back to default.")
            return buf

    def flac_compress(self, buf):
        """
        Placeholder para compresión FLAC.
        En producción se integraría una librería como PyFLAC o se llamaría a un comando externo.
        """
        return buf

    def lossless_image_compress(self, buf):
        """
        Usa Pillow para comprimir imágenes en formato PNG (lossless).
        Si falla o Pillow no está disponible, se hace fallback a zstd.
        """
        if Image is not None:
            try:
                image = Image.open(io.BytesIO(buf))
                output = io.BytesIO()
                image.save(output, format="PNG")
                return output.getvalue()
            except Exception as e:
                print(f"Image compression failed: {e}")
        return zstd.compress(buf, 22) if zstd else buf  # Pass compression level as a positional argument

    def lossless_video_compress(self, buf):
        """
        Placeholder para compresión de video sin pérdida.
        En producción se integrarán herramientas como FFmpeg en modo lossless.
        """
        return buf

    def write(self, path, buf, offset, fh):
        """
        Compress the buffer, split it into blocks, and encapsulate each block
        in a QuantumFigure. Entangle each block with the previous one.
        """
        if path not in self.files:
            self.files[path] = {'blocks': []}
        method, compressed_buf = self.choose_compression(path, buf)  # Use selected compression
        self.files[path]['compression'] = method
        if len(compressed_buf) == 0:
            raise ValueError(f"Error: El archivo '{path}' comprimido tiene tamaño 0.")
        prev_qf = None  # Reference to the previous QuantumFigure
        for i in range(0, len(compressed_buf), self.block_size):
            block = compressed_buf[i:i + self.block_size]
            if len(block) == 0:
                continue  # Ignora bloques vacíos
            block_hash = self.hash_block(block)
            if block_hash not in self.data_blocks:
                self.data_blocks[block_hash] = block
                meta = self.advanced_circular_encode(block, i)  # Use advanced encoding
                self.circular_positions[block_hash] = (meta['radius'], meta['angle'])
                self.circular_metadata[block_hash] = meta  # Store extended metadata
                # Create a QuantumFigure for this block
                qf = QuantumFigure("circle", block, block_size=self.block_size)
                self.quantum_figures[block_hash] = qf
                # Entangle with the previous QuantumFigure
                if prev_qf is not None:
                    prev_qf.entangle(qf)
                prev_qf = qf
            self.files[path]['blocks'].append(block_hash)
        self.periodic_maintenance()  # Llamar al mantenimiento periódico después de escribir
        self.update_global_index()  # Actualizar el índice global después de escribir
        return len(buf)

    def update_global_index(self):
        """
        Actualiza el índice global automáticamente después de cada operación relevante.
        """
        print("Actualizando el índice global...")
        compacted_data = b"".join(self.data_blocks.values())
        compacted_qf = QuantumFigure("global", compacted_data, block_size=self.block_size)
        self.quantum_figures["global_index"] = compacted_qf
        print("Índice global actualizado.")

    def clear_mountpoint(self):
        """
        Vacía el directorio del mount point eliminando todos los archivos.
        """
        print(f"Vaciando el mount point: {self.mountpoint}")
        for root, dirs, files in os.walk(self.mountpoint):
            for file in files:
                os.remove(os.path.join(root, file))
        print("Mount point vaciado correctamente.")

    def compact_global_index(self):
        """
        Compacta el índice global combinando bloques redundantes y eliminando duplicados.
        """
        with self.fs_lock:
            print("Iniciando compactación del índice global...")
            compacted_data = b""
            unique_blocks = set()
            for block_hash, block_data in self.data_blocks.items():
                if block_hash not in unique_blocks:
                    compacted_data += block_data
                    unique_blocks.add(block_hash)

            # Crear un QuantumFigure para el índice compacto
            compacted_qf = QuantumFigure("global", compacted_data, block_size=self.block_size)
            self.files["compacted_index"] = {
                'blocks': [self.hash_block(compacted_data)]
            }
            self.quantum_figures[self.hash_block(compacted_data)] = compacted_qf

            # Guardar el índice global en un archivo
            with open(self.global_index_path, "wb") as f:
                pickle.dump(compacted_qf, f)

            print(f"Índice global compactado. Tamaño: {len(compacted_data)} bytes.")

    def garbage_collect_orphans(self):
        """
        Elimina bloques huérfanos (no referenciados por ningún archivo) del índice global.
        """
        with self.fs_lock:
            referenced = set()
            for file_entry in self.files.values():
                referenced.update(file_entry.get('blocks', []))
            orphan_keys = set(self.data_blocks.keys()) - referenced
            for key in orphan_keys:
                del self.data_blocks[key]
                self.circular_positions.pop(key, None)
                self.circular_metadata.pop(key, None)
                self.quantum_figures.pop(key, None)
            print(f"Recolección de basura completada. Bloques huérfanos eliminados: {len(orphan_keys)}")

    def rebuild_merkle_tree(self):
        """
        Reconstruye el árbol Merkle basado en los bloques actualmente referenciados.
        """
        self.merkle_tree = Node("root")
        for file_entry in self.files.values():
            for block_hash in file_entry.get('blocks', []):
                self.add_to_merkle_tree(block_hash)
        print("Árbol Merkle reconstruido.")

    def periodic_maintenance(self):
        """
        Realiza tareas de mantenimiento periódico, como compactación del índice y recolección de basura.
        """
        print("Iniciando mantenimiento periódico...")
        self.garbage_collect_orphans()
        self.compact_global_index()
        self.rebuild_merkle_tree()
        print("Mantenimiento periódico completado.")

    def snapshot(self, name):
        self.snapshots[name] = pickle.dumps((self.files, self.data_blocks, DictExporter().export(self.merkle_tree)))

    def mount(self):
        """Attempt to mount the filesystem. If FUSE mounting is not available, fall back to physical copy mode."""
        # Try to use FUSE if available and implemented; otherwise create physical copies in mountpoint
        print(f"DEBUG: enable_fuse={self.enable_fuse}")
        if self.enable_fuse and hasattr(fuse, 'FUSE'):
            # Try to mount using fuse.FUSE; fall back to physical copy mode on failure
            try:
                print("DEBUG: Llamando a fuse.FUSE para montar el sistema de archivos...")
                fuse.FUSE(self, self.mountpoint, nothreads=True, foreground=True)
                self.mounted = True
                return
            except BaseException as e:
                print(f"DEBUG: Error desconocido al montar el sistema de archivos: {e}")
                print("INFO: FUSE mount failed, falling back to physical copy mode.")
        # Fallback: populate mountpoint with reconstructed physical files
        print(f"INFO: Falling back to physical copy mode; writing files to {self.mountpoint}")
        os.makedirs(self.mountpoint, exist_ok=True)
        for path in list(self.files.keys()):
            if path == 'compacted_index':
                continue
            content = self.read(path, 10**9, 0, None)
            outpath = os.path.join(self.mountpoint, path)
            os.makedirs(os.path.dirname(outpath), exist_ok=True)
            with open(outpath, 'wb') as f:
                f.write(content)
        self.mounted = True
        print("INFO: Physical copy mode populated.")

    def unmount(self):
        """Unmount or cleanup any state; for physical copy mode this is a no-op."""
        print("INFO: unmount called; nothing to do in physical copy mode.")
        self.mounted = False

    def restore_snapshot(self, name):
        if name in self.snapshots:
            self.files, self.data_blocks, tree_dict = pickle.loads(self.snapshots[name])
            self.merkle_tree = Node.from_dict(tree_dict)
            # Ensure circular metadata and positions are restored properly
            self.circular_positions = {hash: (0, 0) for hash in self.data_blocks}  # Reset positions
            self.circular_metadata = {}  # Reset metadata

    def create_global_figure(self, path):
        """
        Create a global QuantumFigure that unifies all QuantumFigures
        associated with a file by combining their metadata and states.
        """
        if path not in self.files:
            return None
        blocks = self.files[path]['blocks']
        if not blocks:
            return None

        # Function to extract metadata and state from each block
        def extract_metadata(block_hash):
            qf = self.quantum_figures.get(block_hash)
            if qf is not None:
                meta = qf.metadata
                return np.array([
                    meta.get('radius', 0.0),
                    meta.get('angle', 0.0),
                    meta.get('diameter', 0.0),
                    meta.get('density', 0.0),
                    meta.get('overlap', 0.0)
                ], dtype=float), qf.collapse_state()
            else:
                block = self.data_blocks.get(block_hash, b"")
                return np.zeros(5, dtype=float), block

        # Parallel extraction of metadata and states
        metadata_list = []
        state_list = []
        with concurrent.futures.ThreadPoolExecutor() as executor:
            futures = {executor.submit(extract_metadata, bh): bh for bh in blocks}
            for future in concurrent.futures.as_completed(futures):
                meta_vec, state = future.result()
                metadata_list.append(meta_vec)
                state_list.append(state)

        # Combine metadata (e.g., calculate the mean)
        if metadata_list:
            global_meta = np.mean(metadata_list, axis=0)
        else:
            global_meta = np.zeros(5, dtype=float)

        # Combine states (e.g., concatenate and reverse)
        combined_state = b"".join(state_list)[::-1]

        # Create the global QuantumFigure
        global_qf = QuantumFigure("global", combined_state, block_size=self.block_size)
        global_qf.metadata = {
            'radius': float(global_meta[0]),
            'angle': float(global_meta[1]),
            'diameter': float(global_meta[2]),
            'density': float(global_meta[3]),
            'overlap': float(global_meta[4])
        }

        return global_qf

    def measure_redundancy(self):
        """
        Measure empirical redundancy by comparing total block occurrences to unique blocks.
        Returns the percentage of redundant blocks.
        """
        total_blocks = 0
        for file_entry in self.files.values():
            total_blocks += len(file_entry.get('blocks', []))
        unique_blocks = len(self.data_blocks)
        if total_blocks == 0:
            return 0.0
        redundancy_ratio = 1.0 - (unique_blocks / total_blocks)
        print(f"Total blocks: {total_blocks}, Unique blocks: {unique_blocks}, Redundancy: {redundancy_ratio*100:.2f}%")
        return redundancy_ratio

    def superpose_similar_quantum_figures(self, precision=2):
        """
        Fusiona QuantumFigures similares utilizando una combinación ponderada de características
        y una compresión avanzada para los estados fusionados.
        """
        groups = {}
        with self.fs_lock:
            for bh, qf in list(self.quantum_figures.items()):
                meta = qf.metadata
                key = tuple(round(meta.get(k, 0), precision) for k in ['radius', 'angle', 'diameter', 'density', 'overlap'])
                groups.setdefault(key, []).append(bh)

            for key, hashes in groups.items():
                if len(hashes) > 1:
                    # Reconstruct original states and merge losslessly using MPS concatenation and re-decompose
                    state_list = []
                    for h in hashes:
                        qf = self.quantum_figures.get(h)
                        state = qf.collapse_state() if qf is not None else self.data_blocks.get(h, b"")
                        state_list.append(state)

                    merged_data = b"".join(state_list)

                    # Build merged QuantumFigure using merged_data and MPS where possible
                    merged_qf = QuantumFigure("merged", merged_data, block_size=self.block_size)
                    merged_hash = self.hash_block(merged_qf.states['default']) if merged_qf.states.get('default') else self.hash_block(merged_data)

                    # Register merged block
                    self.data_blocks[merged_hash] = merged_qf.states.get('default', merged_data)
                    self.quantum_figures[merged_hash] = merged_qf

                    # For each file, replace contiguous runs of hashes from this group with a merged block specific to that run
                    for fname, fentry in self.files.items():
                        # skip the special compacted_index file
                        if fname == 'compacted_index':
                            continue
                        blocks = fentry.get('blocks', [])
                        new_blocks = []
                        i = 0
                        while i < len(blocks):
                            if blocks[i] in hashes:
                                # start of run
                                j = i
                                run_parts = []
                                while j < len(blocks) and blocks[j] in hashes:
                                    run_parts.append(blocks[j])
                                    j += 1
                                # merged data for this run; prefer quantum_figures collapse_state
                                run_data = b"".join(
                                    (self.quantum_figures[bh].collapse_state() if bh in self.quantum_figures else self.data_blocks.get(bh, b""))
                                    for bh in run_parts
                                )
                                # If run_data is very large, split into chunks of self.block_size to keep blocks manageable
                                if len(run_data) > self.block_size:
                                    k = 0
                                    while k < len(run_data):
                                        chunk = run_data[k:k + self.block_size]
                                        chunk_hash = self.hash_block(chunk)
                                        if chunk_hash not in self.quantum_figures:
                                            chunk_qf = QuantumFigure("merged_chunk", chunk, block_size=self.block_size)
                                            self.quantum_figures[chunk_hash] = chunk_qf
                                            self.data_blocks[chunk_hash] = chunk_qf.states.get('default', chunk)
                                        new_blocks.append(chunk_hash)
                                        k += self.block_size
                                    print(f"DEBUG: file {fname}: replaced run {run_parts} -> {len(new_blocks)} chunk(s) (total len {len(run_data)})")
                                else:
                                    run_hash = self.hash_block(run_data)
                                    if run_hash not in self.quantum_figures:
                                        run_qf = QuantumFigure("merged", run_data, block_size=self.block_size)
                                        self.quantum_figures[run_hash] = run_qf
                                        self.data_blocks[run_hash] = run_qf.states.get('default', run_data)
                                    new_blocks.append(run_hash)
                                    print(f"DEBUG: file {fname}: replaced run {run_parts} -> {run_hash} (len {len(run_data)})")
                                i = j
                            else:
                                new_blocks.append(blocks[i])
                                i += 1
                        fentry['blocks'] = new_blocks

                    # After replacing runs, remove old hashes if no longer referenced
                    referenced = set()
                    for fentry in self.files.values():
                        referenced.update(fentry.get('blocks', []))
                    for h in hashes:
                        if h not in referenced:
                            self.data_blocks.pop(h, None)
                            self.quantum_figures.pop(h, None)

                    print(f"Merged {len(hashes)} quantum figures into merged blocks for key {key}.")

    def test_reconstruction(self, path):
        """
        Test the reconstruction of a file to ensure data integrity.
        """
        if path not in self.files:
            print(f"Archivo no encontrado: {path}")
            return
        reconstructed_data = self.read(path, size=sys.maxsize, offset=0, fh=None)
        original_data = b"".join(self.data_blocks[block] for block in self.files[path]['blocks'])
        if reconstructed_data == original_data:
            print(f"Reconstrucción exitosa para el archivo: {path}")
        else:
            print(f"Error en la reconstrucción del archivo: {path}")

    def mount(self):
        """Attempt to mount the filesystem. If FUSE mounting is not available or disabled, fall back to physical copy mode."""
        print(f"DEBUG: Intentando montar el sistema de archivos en {self.mountpoint}...")
        try:
            prepare_mountpoint(self.mountpoint)  # Usar prepare_mountpoint
            # Try to clean any existing mount state
            try:
                unmount_filesystem(self.mountpoint)
            except Exception as e:
                print(f"DEBUG: Error al intentar desmontar el punto de montaje: {e}")
        except Exception as e:
            print(f"DEBUG: Error al preparar el punto de montaje: {e}")

        # If FUSE enabled explicitly, try to mount; otherwise fallback to physical copy
        if getattr(self, 'enable_fuse', False) and hasattr(fuse, 'FUSE'):
            try:
                print("DEBUG: Llamando a fuse.FUSE para montar el sistema de archivos...")
                fuse.FUSE(self, self.mountpoint, nothreads=True, foreground=True)
                self.mounted = True
                self.fuse_mounted = True
                return
            except Exception as e:
                print(f"DEBUG: FUSE mount error: {e}; falling back to physical copy mode.")

        # Fallback: populate mountpoint with reconstructed physical files
        print(f"INFO: Falling back to physical copy mode; writing files to {self.mountpoint}")
        self.fuse_mounted = False
        os.makedirs(self.mountpoint, exist_ok=True)
        for path in list(self.files.keys()):
            if path == 'compacted_index':
                continue
            try:
                content = self.read(path, 10**9, 0, None)
                outpath = os.path.join(self.mountpoint, path)
                os.makedirs(os.path.dirname(outpath), exist_ok=True)
                with open(outpath, 'wb') as f:
                    f.write(content)
            except Exception as e:
                print(f"WARNING: Could not write {path} to mountpoint: {e}")
        self.mounted = True
        print("INFO: Physical copy mode populated.")

if __name__ == '__main__':
    mountpoint = sys.argv[1] if len(sys.argv) > 1 else '/mnt'
    fs = DedupFS(mountpoint)
    fs.mount()