"""
Implementación de File para SharePoint usando Microsoft Graph API.
"""
from __future__ import annotations
from typing import Optional, Dict, Any, BinaryIO, TYPE_CHECKING
from datetime import datetime
from pathlib import Path
from urllib.parse import quote
import io

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

from tai_alphi import Alphi
from ...base.file import File
from ...exceptions import FileNotFoundError, FileAlreadyExistsError, WriteError

if TYPE_CHECKING:
    from .folder import SharePointFolder

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


class SharePointFile(File):
    """
    Representa un archivo en SharePoint Online.
    
    Args:
        folder: SharePointFolder que contiene el archivo
        filename: Nombre del archivo
        full_path: Ruta completa desde la raíz del drive
    """
    
    def __init__(self, folder: SharePointFolder, filename: str, full_path: str):
        super().__init__(folder, filename, full_path)
        self._item_id: Optional[str] = None
        self._metadata: Optional[Dict[str, Any]] = None

        self._session = requests.Session()
        retries = Retry(
            total=8,
            connect=8,
            read=3,
            backoff_factor=0.5,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "HEAD", "PUT", "PATCH", "DELETE"],
            raise_on_status=False,
        )
        self._session.mount("https://", HTTPAdapter(max_retries=retries))
    def exists(self) -> bool:
        """Verifica si el archivo existe en SharePoint."""
        try:
            self._get_metadata()
            return True
        except (FileNotFoundError, requests.HTTPError):
            return False
    
    def _get_metadata(self, force_refresh: bool = False) -> Dict[str, Any]:
        """Obtiene los metadatos del archivo desde Graph API."""
        if self._metadata and not force_refresh:
            return self._metadata

        path = self.full_path.strip('/')
        encoded_path = '/'.join(quote(part, safe='') for part in path.split('/'))

        # OJO: para path addressing es más robusto terminar con ':'
        url = f"https://graph.microsoft.com/v1.0/drives/{self.folder.origin.drive_id}/root:/{encoded_path}:"

        params = {
            # Garantiza que venga downloadUrl y lo típico que necesitas
            "$select": "id,name,size,webUrl,file,createdDateTime,lastModifiedDateTime,@microsoft.graph.downloadUrl"
        }

        logger.debug(f"Obteniendo metadatos: {url}")
        response = self._session.get(
            url,
            headers=self.folder.origin.headers,
            params=params,
            timeout=(10, 30),
        )

        if response.status_code == 404:
            raise FileNotFoundError(f"El archivo '{self.filename}' no existe")

        response.raise_for_status()
        data = response.json()

        if "file" not in data:
            raise FileNotFoundError(f"'{self.full_path}' no es un archivo")

        self._metadata = data
        self._item_id = data["id"]
        return self._metadata

    
    def read(self) -> bytes:
        metadata = self._get_metadata()

        download_url = metadata.get("@microsoft.graph.downloadUrl")

        if download_url:
            # Descarga directa: sin headers, pero con stream y timeout
            with self._session.get(download_url, stream=True, timeout=(10, 300)) as r:
                r.raise_for_status()
                return b"".join(r.iter_content(chunk_size=1024 * 1024))

        # Fallback: endpoint Graph de contenido
        url = f"https://graph.microsoft.com/v1.0/drives/{self.folder.origin.drive_id}/items/{self._item_id}/content"
        with self._session.get(url, headers=self.folder.origin.headers, stream=True, timeout=(10, 300)) as r:
            r.raise_for_status()
            return b"".join(r.iter_content(chunk_size=1024 * 1024))
    
    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
        """
        content = self.read()
        return content.decode(encoding)
    
    def read_stream(self) -> BinaryIO:
        """
        Obtiene un stream de lectura del archivo.
        
        Returns:
            Stream binario de lectura (BytesIO)
        """
        content = self.read()
        return io.BytesIO(content)
    
    def write(self, data: bytes, overwrite: bool = True) -> None:
        from ...exceptions import FileAlreadyExistsError

        if self.exists() and not overwrite:
            raise FileAlreadyExistsError(f"El archivo '{self.filename}' ya existe")

        path = self.full_path.strip('/')
        encoded_path = '/'.join(quote(part, safe='') for part in path.split('/'))

        url = f"https://graph.microsoft.com/v1.0/drives/{self.folder.origin.drive_id}/root:/{encoded_path}:/content"

        headers = self.folder.origin.headers.copy()
        headers["Content-Type"] = "application/octet-stream"

        response = self._session.put(url, headers=headers, data=data, timeout=(10, 300))

        if not response.ok:
            raise WriteError(f"Error escribiendo archivo: {response.text}")

        self._metadata = None
        self._item_id = None

        logger.info(f"✓ Archivo escrito: {self.filename}")

    
    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
        """
        data = text.encode(encoding)
        self.write(data, overwrite=overwrite)
    
    def delete(self) -> None:
        """Elimina el archivo de SharePoint."""
        item_id = self._get_metadata()["id"]
        
        url = f"https://graph.microsoft.com/v1.0/drives/{self.folder.origin.drive_id}/items/{item_id}"
        
        response = self._session.delete(url, headers=self.folder.origin.headers, timeout=(10, 300))
        response.raise_for_status()
        
        logger.info(f"✓ Archivo eliminado: {self.filename}")
    
    def copy_to(self, destination: File, overwrite: bool = False) -> None:
        """
        Copia el archivo a otro destino.
        
        Args:
            destination: Archivo destino (puede ser SharePointFile u otro)
            overwrite: Si True, sobrescribe si existe
        """
        from ...exceptions import FileAlreadyExistsError
        
        # Leer contenido y escribir en destino
        content = self.read()
        
        if destination.exists() and not overwrite:
            raise FileAlreadyExistsError(f"El archivo destino '{destination.filename}' ya existe")
        
        destination.write(content, overwrite=overwrite)
        logger.info(f"✓ Archivo copiado: {self.filename} → {destination.filename}")
    
    def move_to(self, destination: File, overwrite: bool = False) -> None:
        """
        Mueve el archivo a otro destino.
        
        Args:
            destination: Archivo destino
            overwrite: Si True, sobrescribe si existe
        """
        # Si el destino es también SharePointFile en el mismo drive, usar API nativa
        from .file import SharePointFile
        
        if isinstance(destination, SharePointFile):
            if destination.folder.origin.drive_id == self.folder.origin.drive_id:
                self._move_native(destination, overwrite)
                return
        
        # Fallback: copiar y eliminar
        self.copy_to(destination, overwrite)
        self.delete()
        logger.info(f"✓ Archivo movido: {self.filename} → {destination.filename}")
    
    def _move_native(self, destination: SharePointFile, overwrite: bool) -> None:
        """Mueve el archivo usando la API nativa de SharePoint."""
        item_id = self._get_metadata()["id"]
        
        # Obtener ID de la carpeta destino
        dest_folder_id = destination.folder._get_item_id()
        
        # Preparar payload
        payload = {
            "parentReference": {
                "id": dest_folder_id
            },
            "name": destination.filename
        }
        
        if not overwrite and destination.exists():
            from ...exceptions import FileAlreadyExistsError
            raise FileAlreadyExistsError(f"El archivo destino '{destination.filename}' ya existe")
        
        url = f"https://graph.microsoft.com/v1.0/drives/{self.folder.origin.drive_id}/items/{item_id}"
        
        response = requests.patch(url, headers=self.folder.origin.headers, json=payload)
        response.raise_for_status()
        
        logger.info(f"✓ Archivo movido (nativo): {self.filename} → {destination.filename}")
    
    @property
    def name(self) -> str:
        """Nombre del archivo sin extensión."""
        return Path(self.filename).stem
    
    @property
    def extension(self) -> str:
        """Extensión del archivo (incluye el punto)."""
        return Path(self.filename).suffix
    
    @property
    def size(self) -> int:
        """Tamaño del archivo en bytes."""
        metadata = self._get_metadata()
        return metadata.get("size", 0)
    
    @property
    def created_time(self) -> datetime:
        """Fecha de creación del archivo."""
        metadata = self._get_metadata()
        dt_str = metadata.get("createdDateTime", "")
        return datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
    
    @property
    def modified_time(self) -> datetime:
        """Fecha de última modificación del archivo."""
        metadata = self._get_metadata()
        dt_str = metadata.get("lastModifiedDateTime", "")
        return datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
    
    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(...),
                'item_id': 'id_del_item',
                'web_url': 'https://...',
                ...
            }
        """
        raw_metadata = self._get_metadata()
        
        return {
            'name': self.filename,
            'path': self.full_path,
            'size': self.size,
            'extension': self.extension,
            'created_time': self.created_time,
            'modified_time': self.modified_time,
            'item_id': raw_metadata.get('id'),
            'web_url': raw_metadata.get('webUrl'),
            'raw': raw_metadata  # Incluir metadatos completos de Graph API
        }
