"""
File handling utilities for VFX Project Management System.
Provides secure file upload, validation, and serving functionality.
"""

import os
import shutil
import mimetypes
from datetime import datetime
from typing import Optional, List, Tuple
from pathlib import Path
from PIL import Image
from fastapi import HTTPException, UploadFile
from sqlalchemy.orm import Session
import hashlib


class FileHandler:
    """Handles file operations for the VFX system."""
    
    # Supported VFX media formats
    SUPPORTED_FORMATS = {
        # Video formats
        '.mov', '.mp4', '.avi', '.mkv', '.webm',
        # Image formats
        '.exr', '.jpg', '.jpeg', '.png', '.tiff', '.tif', '.dpx', '.hdr',
        # Document formats
        '.pdf', '.txt', '.doc', '.docx',
        # Archive formats
        '.zip', '.rar', '.7z'
    }
    
    # File size limits (in bytes)
    MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024  # 10MB for attachments
    MAX_SUBMISSION_SIZE = 500 * 1024 * 1024  # 500MB for submissions (fallback)
    
    # Movie file extensions that should use global upload limit
    MOVIE_EXTENSIONS = {'.mov', '.mp4', '.avi', '.mkv', '.webm'}
    
    # Thumbnail settings
    THUMBNAIL_SIZE = (200, 200)
    THUMBNAIL_QUALITY = 85
    
    def __init__(self, base_upload_dir: str = "uploads"):
        # Use absolute path relative to this file's location
        # This ensures it works whether run from workspace root or backend directory
        current_file = Path(__file__).resolve()
        self.backend_dir = current_file.parent.parent  # Go up from utils/ to backend/
        self.base_upload_dir = self.backend_dir / base_upload_dir
        self.base_upload_dir.mkdir(parents=True, exist_ok=True)
        
        # Create subdirectories
        self.attachments_dir = self.base_upload_dir / "attachments"
        self.submissions_dir = self.base_upload_dir / "submissions"
        self.thumbnails_dir = self.base_upload_dir / "thumbnails"
        self.project_thumbnails_dir = self.base_upload_dir / "project_thumbnails"
        
        for directory in [self.attachments_dir, self.submissions_dir, self.thumbnails_dir, self.project_thumbnails_dir]:
            directory.mkdir(parents=True, exist_ok=True)
    
    def validate_file(self, file: UploadFile, max_size: int, db: Optional[Session] = None) -> None:
        """Validate uploaded file format and size."""
        if not file.filename:
            raise HTTPException(status_code=400, detail="No filename provided")
        
        # Check file extension
        file_extension = Path(file.filename).suffix.lower()
        if file_extension not in self.SUPPORTED_FORMATS:
            raise HTTPException(
                status_code=400,
                detail=f"Unsupported file format. Supported formats: {', '.join(sorted(self.SUPPORTED_FORMATS))}"
            )
        
        # For movie files, use global upload limit if available
        effective_max_size = max_size
        if file_extension in self.MOVIE_EXTENSIONS and db:
            global_limit = self.get_global_upload_limit(db)
            if global_limit:
                effective_max_size = global_limit
        
        # Check file size (we'll read content later, so this is a preliminary check)
        if hasattr(file, 'size') and file.size and file.size > effective_max_size:
            size_mb = effective_max_size // (1024*1024)
            raise HTTPException(
                status_code=413,
                detail=f"File too large. Maximum size is {size_mb}MB"
            )
    
    def generate_unique_filename(self, original_filename: str, prefix: str = "") -> str:
        """Generate a unique filename with timestamp and hash."""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        file_stem = Path(original_filename).stem
        file_extension = Path(original_filename).suffix
        
        # Create a short hash for uniqueness
        hash_input = f"{original_filename}{timestamp}".encode()
        short_hash = hashlib.md5(hash_input).hexdigest()[:8]
        
        if prefix:
            return f"{prefix}_{file_stem}_{timestamp}_{short_hash}{file_extension}"
        else:
            return f"{file_stem}_{timestamp}_{short_hash}{file_extension}"
    
    def create_directory_structure(self, task_id: int, file_type: str) -> Path:
        """Create organized directory structure for file storage."""
        if file_type == "attachment":
            base_dir = self.attachments_dir
        elif file_type == "submission":
            base_dir = self.submissions_dir
        else:
            raise ValueError(f"Unknown file type: {file_type}")
        
        # Create task-specific directory
        task_dir = base_dir / str(task_id)
        task_dir.mkdir(exist_ok=True)
        
        return task_dir
    
    def get_global_upload_limit(self, db: Session) -> Optional[int]:
        """Get the global upload limit in bytes from database."""
        try:
            from models.global_settings import GlobalSettings
            
            setting = db.query(GlobalSettings).filter(
                GlobalSettings.setting_key == "global_upload_limit_mb"
            ).first()
            
            if setting:
                limit_mb = int(setting.setting_value)
                return limit_mb * 1024 * 1024  # Convert MB to bytes
            
        except Exception:
            pass
        
        return None
    
    def store_relative_path(self, absolute_path: str) -> str:
        """Convert absolute path to relative path for database storage."""
        absolute_path_obj = Path(absolute_path).resolve()
        try:
            # Convert to relative path from backend directory
            relative_path = absolute_path_obj.relative_to(self.backend_dir)
            return str(relative_path).replace('\\', '/')  # Use forward slashes for consistency
        except ValueError:
            # If path is not under backend directory, return as-is
            # This handles edge cases during migration
            return str(absolute_path_obj).replace('\\', '/')
    
    def resolve_absolute_path(self, stored_path: str) -> str:
        """Resolve stored path (relative or absolute) to absolute path."""
        stored_path_obj = Path(stored_path)
        
        # If already absolute, return as-is
        if stored_path_obj.is_absolute():
            return str(stored_path_obj)
        
        # Otherwise, resolve relative to backend directory
        absolute_path = self.backend_dir / stored_path_obj
        return str(absolute_path.resolve())
    
    def is_relative_path(self, path: str) -> bool:
        """Check if a path is relative to backend directory."""
        path_obj = Path(path)
        return not path_obj.is_absolute()
    
    def migrate_path_to_relative(self, absolute_path: str) -> str:
        """Convert legacy absolute path to new relative format."""
        return self.store_relative_path(absolute_path)
    
    async def save_file(self, file: UploadFile, task_id: int, file_type: str, 
                       version_number: Optional[int] = None, db: Optional[Session] = None) -> Tuple[str, int]:
        """Save uploaded file and return relative file path and size."""
        # Read file content
        file_content = await file.read()
        file_size = len(file_content)
        
        # Determine max size based on file type and global settings
        max_size = self.MAX_SUBMISSION_SIZE if file_type == "submission" else self.MAX_ATTACHMENT_SIZE
        
        # For movie files, use global upload limit if available
        file_extension = Path(file.filename).suffix.lower()
        if file_extension in self.MOVIE_EXTENSIONS and db:
            global_limit = self.get_global_upload_limit(db)
            if global_limit:
                max_size = global_limit
        
        # Validate size after reading
        if file_size > max_size:
            size_mb = max_size // (1024*1024)
            raise HTTPException(
                status_code=413,
                detail=f"File too large. Maximum size is {size_mb}MB"
            )
        
        # Create directory structure
        task_dir = self.create_directory_structure(task_id, file_type)
        
        # Generate unique filename
        prefix = f"v{version_number:03d}" if version_number else ""
        unique_filename = self.generate_unique_filename(file.filename, prefix)
        file_path = task_dir / unique_filename
        
        # Save file
        with open(file_path, "wb") as buffer:
            buffer.write(file_content)
        
        # Return relative path for database storage
        relative_path = self.store_relative_path(str(file_path))
        return relative_path, file_size
    
    def delete_file(self, file_path: str) -> bool:
        """Delete a file from the filesystem."""
        try:
            # Resolve to absolute path for filesystem operations
            absolute_path = self.resolve_absolute_path(file_path)
            
            if os.path.exists(absolute_path):
                os.remove(absolute_path)
                
                # Also delete thumbnail if it exists
                thumbnail_path = self.get_thumbnail_path(file_path)
                absolute_thumbnail_path = self.resolve_absolute_path(thumbnail_path)
                if os.path.exists(absolute_thumbnail_path):
                    os.remove(absolute_thumbnail_path)
                
                return True
        except Exception:
            pass
        return False
    
    def get_thumbnail_path(self, file_path: str) -> str:
        """Get the thumbnail path for a given file (returns relative path)."""
        # Resolve to absolute path to get the filename
        absolute_path = self.resolve_absolute_path(file_path)
        file_path_obj = Path(absolute_path)
        thumbnail_name = f"{file_path_obj.stem}_thumb.jpg"
        
        # Return relative path for consistency
        absolute_thumbnail_path = self.thumbnails_dir / thumbnail_name
        return self.store_relative_path(str(absolute_thumbnail_path))
    
    def create_thumbnail(self, file_path: str) -> Optional[str]:
        """Create a thumbnail for image files (returns relative path)."""
        try:
            # Resolve to absolute path for filesystem operations
            absolute_file_path = self.resolve_absolute_path(file_path)
            file_extension = Path(absolute_file_path).suffix.lower()
            
            # Only create thumbnails for image formats (excluding EXR for now due to complexity)
            image_formats = {'.jpg', '.jpeg', '.png', '.tiff', '.tif'}
            if file_extension not in image_formats:
                return None
            
            # Open and resize image
            with Image.open(absolute_file_path) as img:
                # Convert to RGB if necessary
                if img.mode in ('RGBA', 'LA', 'P'):
                    img = img.convert('RGB')
                
                # Create thumbnail
                img.thumbnail(self.THUMBNAIL_SIZE, Image.Resampling.LANCZOS)
                
                # Get thumbnail path (relative)
                thumbnail_path = self.get_thumbnail_path(file_path)
                absolute_thumbnail_path = self.resolve_absolute_path(thumbnail_path)
                
                # Save thumbnail
                img.save(absolute_thumbnail_path, 'JPEG', quality=self.THUMBNAIL_QUALITY)
                
                return thumbnail_path  # Return relative path
        
        except Exception as e:
            # Log error but don't fail the upload
            print(f"Failed to create thumbnail for {file_path}: {e}")
            return None
    
    def get_file_info(self, file_path: str) -> dict:
        """Get file information including size, type, and modification time."""
        try:
            # Resolve to absolute path for filesystem operations
            absolute_path = self.resolve_absolute_path(file_path)
            file_path_obj = Path(absolute_path)
            
            if not file_path_obj.exists():
                return {'exists': False}
            
            stat = file_path_obj.stat()
            mime_type, _ = mimetypes.guess_type(str(file_path_obj))
            
            return {
                'size': stat.st_size,
                'mime_type': mime_type or 'application/octet-stream',
                'modified_at': datetime.fromtimestamp(stat.st_mtime),
                'exists': True
            }
        except Exception:
            return {'exists': False}
    
    def is_image_file(self, file_path: str) -> bool:
        """Check if file is an image."""
        # Works with both relative and absolute paths
        file_extension = Path(file_path).suffix.lower()
        image_formats = {'.jpg', '.jpeg', '.png', '.tiff', '.tif', '.exr', '.hdr', '.dpx'}
        return file_extension in image_formats
    
    def is_video_file(self, file_path: str) -> bool:
        """Check if file is a video."""
        # Works with both relative and absolute paths
        file_extension = Path(file_path).suffix.lower()
        video_formats = {'.mov', '.mp4', '.avi', '.mkv', '.webm'}
        return file_extension in video_formats


# Global file handler instance
file_handler = FileHandler()