"""
Clase base abstracta para File (archivos/blobs).
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any, BinaryIO, TYPE_CHECKING
from datetime import datetime
from pathlib import Path

from tai_alphi import Alphi

from ..data import FileDataReader  # Importación para get_data()

if TYPE_CHECKING:
    from .folder import Folder

logger = Alphi.get_logger_by_name('tai-storage')


class File(ABC):
    """
    Clase base abstracta que representa un archivo en un sistema de almacenamiento.
    
    Un archivo puede ser un archivo local, un blob de Azure, un archivo en SFTP, etc.
    """
    
    def __init__(self, folder: Folder, filename: str, full_path: str):
        """
        Inicializa el archivo.
        
        Args:
            folder: Carpeta a la que pertenece el archivo
            filename: Nombre del archivo
            full_path: Ruta completa del archivo
        """
        self.folder = folder
        self.filename = filename
        self.full_path = full_path
        logger.debug(f"Initializing {self.__class__.__name__}: {filename}")
    
    @property
    @abstractmethod
    def name(self) -> str:
        """
        Nombre del archivo sin extensión.
        
        Returns:
            Nombre base del archivo
        """
        pass
    
    @property
    @abstractmethod
    def extension(self) -> str:
        """
        Extensión del archivo.
        
        Returns:
            Extensión incluyendo el punto (ej: '.txt', '.csv')
        """
        pass
    
    @property
    @abstractmethod
    def size(self) -> int:
        """
        Tamaño del archivo en bytes.
        
        Returns:
            Tamaño en bytes
            
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @property
    @abstractmethod
    def created_time(self) -> datetime:
        """
        Fecha y hora de creación del archivo.
        
        Returns:
            Datetime de creación
            
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @property
    @abstractmethod
    def modified_time(self) -> datetime:
        """
        Fecha y hora de última modificación.
        
        Returns:
            Datetime de modificación
            
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @abstractmethod
    def read(self) -> bytes:
        """
        Lee el contenido completo del archivo.
        
        Returns:
            Contenido del archivo como bytes
            
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @abstractmethod
    def read_text(self, encoding: str = 'utf-8') -> str:
        """
        Lee el contenido del archivo como texto.
        
        Args:
            encoding: Codificación del texto
            
        Returns:
            Contenido como string
            
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @abstractmethod
    def read_stream(self) -> BinaryIO:
        """
        Obtiene un stream de lectura del archivo.
        
        Returns:
            Stream binario de lectura
            
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @abstractmethod
    def write(self, data: bytes, overwrite: bool = True) -> None:
        """
        Escribe datos en el archivo.
        
        Args:
            data: Datos a escribir (bytes)
            overwrite: Si True, sobrescribe; si False, lanza error si existe
            
        Raises:
            FileAlreadyExistsError: Si existe y overwrite=False
        """
        pass
    
    @abstractmethod
    def write_text(self, text: str, encoding: str = 'utf-8', overwrite: bool = True) -> None:
        """
        Escribe texto en el archivo.
        
        Args:
            text: Texto a escribir
            encoding: Codificación del texto
            overwrite: Si True, sobrescribe; si False, lanza error si existe
            
        Raises:
            FileAlreadyExistsError: Si existe y overwrite=False
        """
        pass
    
    @abstractmethod
    def delete(self) -> None:
        """
        Elimina el archivo.
        
        Raises:
            FileNotFoundError: Si el archivo no existe
        """
        pass
    
    @abstractmethod
    def exists(self) -> bool:
        """
        Verifica si el archivo existe.
        
        Returns:
            True si existe, False en caso contrario
        """
        pass
    
    @abstractmethod
    def copy_to(self, destination: File, overwrite: bool = False) -> None:
        """
        Copia el archivo a otro destino.
        
        Args:
            destination: Archivo destino (puede ser de otro origen)
            overwrite: Si True, sobrescribe si ya existe
            
        Raises:
            FileNotFoundError: Si el archivo origen no existe
            FileAlreadyExistsError: Si destino existe y overwrite=False
        """
        pass
    
    @abstractmethod
    def move_to(self, destination: File, overwrite: bool = False) -> None:
        """
        Mueve el archivo a otro destino.
        
        Args:
            destination: Archivo destino (puede ser de otro origen)
            overwrite: Si True, sobrescribe si ya existe
            
        Raises:
            FileNotFoundError: Si el archivo origen no existe
            FileAlreadyExistsError: Si destino existe y overwrite=False
        """
        pass
    
    @abstractmethod
    def get_metadata(self) -> Dict[str, Any]:
        """
        Obtiene todos los metadatos del archivo.
        
        Returns:
            Diccionario con metadatos:
            {
                'name': 'archivo.txt',
                'path': 'ruta/completa/archivo.txt',
                'size': 1234,
                'extension': '.txt',
                'created_time': datetime(...),
                'modified_time': datetime(...),
                ...
            }
        """
        pass
    
    def download_to_local(self, local_path: str, overwrite: bool = False) -> None:
        """
        Descarga el archivo a una ruta local.
        
        Args:
            local_path: Ruta local donde guardar el archivo
            overwrite: Si True, sobrescribe si ya existe
            
        Raises:
            FileNotFoundError: Si el archivo no existe
            FileAlreadyExistsError: Si el archivo local existe y overwrite=False
        """
        logger.info(f"Downloading {self.filename} to {local_path}")
        
        local_file = Path(local_path)
        
        if local_file.exists() and not overwrite:
            from tai_storage.exceptions import FileAlreadyExistsError
            raise FileAlreadyExistsError(f"Local file already exists: {local_path}")
        
        # Crear directorio padre si no existe
        local_file.parent.mkdir(parents=True, exist_ok=True)
        
        # Leer y escribir
        data = self.read()
        local_file.write_bytes(data)
        
        logger.debug(f"Downloaded {len(data)} bytes to {local_path}")
    
    def get_data(self) -> FileDataReader:
        """
        Obtiene un reader de datos para convertir el archivo a DataFrame.
        
        Proporciona acceso a funcionalidades de conversión a pandas DataFrame
        con detección automática de formato basada en la extensión del archivo.
        
        Returns:
            FileDataReader: Objeto con métodos para convertir a DataFrame
            
        Raises:
            ImportError: Si el módulo 'data' no está instalado
            
        Examples:
            >>> # Auto-detectar formato
            >>> file = folder.get_file('datos.csv')
            >>> df = file.get_data().to_dataframe()
            
            >>> # CSV con opciones
            >>> df = file.get_data().to_csv(sep=';', encoding='latin1')
            
            >>> # Excel con hoja específica
            >>> df = file.get_data().to_excel(sheet_name='Ventas')
            
            >>> # Con punto de entrada
            >>> df = file.get_data().to_excel(
            ...     entrypoint='Código',
            ...     infer_dimensions=True
            ... )
        """
        return FileDataReader(self)
    
    def __repr__(self) -> str:
        """Representación string del archivo."""
        return f"<{self.__class__.__name__} path='{self.full_path}'>"
