import os
import hashlib
import threading
import time
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from flask import current_app
from werkzeug.utils import secure_filename
from app import db
from app.utils.security import sanitize_filename, validate_file_type, validate_file_size, detect_malicious_content
from app.utils.logging import log_file_operation, log_upload_progress
import logging

logger = logging.getLogger(__name__)

class FileUploadHandler:
    """Handle file uploads with progress tracking and security validation"""
    
    def __init__(self):
        self.upload_folder = None
        self.chunk_size = 8192
        self.max_concurrent_uploads = 5
        self.executor = None
        self.active_uploads = {}
        self.upload_lock = threading.Lock()
        self._initialized = False
    
    def _ensure_initialized(self):
        """Ensure handler is initialized with Flask app config"""
        if not self._initialized:
            from flask import current_app
            self.upload_folder = current_app.config.get('UPLOAD_FOLDER')
            self.chunk_size = current_app.config.get('CHUNK_SIZE', 8192)
            self.max_concurrent_uploads = current_app.config.get('MAX_CONCURRENT_UPLOADS', 5)
            if self.executor is None:
                self.executor = ThreadPoolExecutor(max_workers=self.max_concurrent_uploads)
            self._initialized = True
    
    def validate_upload(self, file, file_type):
        """
        Validate file upload
        
        Args:
            file: Werkzeug FileStorage object
            file_type: Expected file type ('fixture' or 'zip')
        
        Returns:
            tuple: (is_valid, error_message)
        """
        try:
            # Check if file is present
            if not file or not file.filename:
                return False, "No file provided"
            
            # Validate file type
            if file_type == 'fixture':
                allowed_extensions = current_app.config['ALLOWED_FIXTURE_EXTENSIONS']
            elif file_type == 'zip':
                allowed_extensions = current_app.config['ALLOWED_ZIP_EXTENSIONS']
            else:
                return False, "Invalid file type specified"
            
            if not validate_file_type(file.filename, allowed_extensions):
                return False, f"File type not allowed. Allowed types: {', '.join(allowed_extensions)}"
            
            # Validate file size
            file.seek(0, os.SEEK_END)
            file_size = file.tell()
            file.seek(0)
            
            if not validate_file_size(file_size):
                max_size_mb = current_app.config.get('MAX_CONTENT_LENGTH', 500 * 1024 * 1024) // (1024 * 1024)
                return False, f"File too large. Maximum size: {max_size_mb}MB"
            
            return True, None
            
        except Exception as e:
            logger.error(f"File validation error: {str(e)}")
            return False, "File validation failed"
    
    def calculate_sha1(self, file_path):
        """
        Calculate SHA1 checksum of file
        
        Args:
            file_path: Path to file
        
        Returns:
            str: SHA1 checksum in hexadecimal
        """
        sha1_hash = hashlib.sha1()
        try:
            with open(file_path, 'rb') as f:
                for chunk in iter(lambda: f.read(self.chunk_size), b""):
                    sha1_hash.update(chunk)
            return sha1_hash.hexdigest()
        except Exception as e:
            logger.error(f"SHA1 calculation error: {str(e)}")
            return None
    
    def save_file_chunked(self, file, file_path, upload_record):
        """
        Save file in chunks with progress tracking
        
        Args:
            file: Werkzeug FileStorage object
            file_path: Destination file path
            upload_record: FileUpload database record
        
        Returns:
            bool: True if successful
        """
        try:
            file.seek(0, os.SEEK_END)
            total_size = file.tell()
            file.seek(0)
            
            bytes_written = 0
            
            with open(file_path, 'wb') as f:
                while True:
                    chunk = file.read(self.chunk_size)
                    if not chunk:
                        break
                    
                    f.write(chunk)
                    bytes_written += len(chunk)
                    
                    # Update progress
                    progress = (bytes_written / total_size) * 100 if total_size > 0 else 100
                    upload_record.update_progress(progress)
                    
                    # Log progress for large files (every 10%)
                    if total_size > 10 * 1024 * 1024 and progress % 10 < 1:
                        log_upload_progress(
                            upload_record.id,
                            round(progress, 2),
                            'uploading',
                            user_id=upload_record.uploaded_by,
                            match_id=upload_record.match_id
                        )
            
            return True
            
        except Exception as e:
            logger.error(f"File save error: {str(e)}")
            upload_record.mark_failed(f"File save failed: {str(e)}")
            return False
    
    def process_upload(self, file, file_type, user_id, match_id=None):
        """
        Process file upload with validation and progress tracking
        
        Args:
            file: Werkzeug FileStorage object
            file_type: Type of file ('fixture' or 'zip')
            user_id: ID of user uploading file
            match_id: Associated match ID (for ZIP files)
        
        Returns:
            tuple: (upload_record, error_message)
        """
        try:
            # Ensure handler is initialized
            self._ensure_initialized()
            
            if self.upload_folder is None:
                logger.error("Upload folder is None after initialization")
                return None, "Upload folder not configured"
            # Validate upload
            is_valid, error_message = self.validate_upload(file, file_type)
            if not is_valid:
                return None, error_message
            
            # Generate secure filename
            original_filename = file.filename
            sanitized_name = sanitize_filename(original_filename)
            timestamp = datetime.utcnow().strftime('%Y%m%d_%H%M%S')
            filename = f"{timestamp}_{sanitized_name}"
            
            # Create upload directory if it doesn't exist
            if self.upload_folder is None:
                logger.error("Upload folder is None - cannot create directory")
                return None, "Upload folder not configured"
            
            try:
                os.makedirs(self.upload_folder, exist_ok=True)
                file_path = os.path.join(self.upload_folder, filename)
            except OSError as e:
                logger.error(f"Failed to create upload directory {self.upload_folder}: {str(e)}")
                return None, f"Failed to create upload directory: {str(e)}"
            
            # Get file size and MIME type
            file.seek(0, os.SEEK_END)
            file_size = file.tell()
            file.seek(0)
            mime_type = file.content_type or 'application/octet-stream'
            
            # Create upload record
            from app.models import FileUpload
            upload_record = FileUpload(
                filename=filename,
                original_filename=original_filename,
                file_path=file_path,
                file_size=file_size,
                file_type=file_type,
                mime_type=mime_type,
                sha1sum='',  # Will be calculated after upload
                upload_status='uploading',
                match_id=match_id,
                uploaded_by=user_id
            )
            
            db.session.add(upload_record)
            db.session.commit()
            
            # Save file with progress tracking
            if not self.save_file_chunked(file, file_path, upload_record):
                return upload_record, "File save failed"
            
            # Calculate SHA1 checksum
            sha1_checksum = self.calculate_sha1(file_path)
            if not sha1_checksum:
                upload_record.mark_failed("Checksum calculation failed")
                return upload_record, "Checksum calculation failed"
            
            # Check for malicious content
            if detect_malicious_content(file_path):
                os.remove(file_path)
                upload_record.mark_failed("Potentially malicious content detected")
                log_file_operation(
                    'MALICIOUS_CONTENT_DETECTED',
                    filename,
                    user_id=user_id,
                    upload_id=upload_record.id,
                    status='BLOCKED'
                )
                return upload_record, "File blocked due to security concerns"
            
            # Update upload record with checksum
            upload_record.sha1sum = sha1_checksum
            upload_record.mark_completed()
            
            log_file_operation(
                'UPLOAD_COMPLETED',
                filename,
                user_id=user_id,
                match_id=match_id,
                upload_id=upload_record.id,
                extra_data={
                    'file_size': file_size,
                    'file_type': file_type,
                    'sha1sum': sha1_checksum
                }
            )
            
            return upload_record, None
            
        except Exception as e:
            logger.error(f"Upload processing error: {str(e)}")
            if 'upload_record' in locals():
                upload_record.mark_failed(f"Upload processing failed: {str(e)}")
            return None, f"Upload processing failed: {str(e)}"
    
    def upload_file_async(self, file, file_type, user_id, match_id=None):
        """
        Upload file asynchronously
        
        Args:
            file: Werkzeug FileStorage object
            file_type: Type of file ('fixture' or 'zip')
            user_id: ID of user uploading file
            match_id: Associated match ID (for ZIP files)
        
        Returns:
            str: Upload ID for tracking
        """
        upload_id = f"{user_id}_{int(time.time())}"
        
        with self.upload_lock:
            if len(self.active_uploads) >= self.max_concurrent_uploads:
                return None, "Maximum concurrent uploads reached"
            
            future = self.executor.submit(
                self.process_upload, file, file_type, user_id, match_id
            )
            self.active_uploads[upload_id] = future
        
        return upload_id, None
    
    def get_upload_status(self, upload_id):
        """
        Get status of async upload
        
        Args:
            upload_id: Upload ID
        
        Returns:
            dict: Upload status information
        """
        with self.upload_lock:
            if upload_id not in self.active_uploads:
                return {'status': 'not_found'}
            
            future = self.active_uploads[upload_id]
            
            if future.done():
                try:
                    upload_record, error_message = future.result()
                    del self.active_uploads[upload_id]
                    
                    if error_message:
                        return {
                            'status': 'failed',
                            'error': error_message,
                            'upload_record': upload_record.to_dict() if upload_record else None
                        }
                    else:
                        return {
                            'status': 'completed',
                            'upload_record': upload_record.to_dict()
                        }
                except Exception as e:
                    del self.active_uploads[upload_id]
                    return {
                        'status': 'failed',
                        'error': str(e)
                    }
            else:
                return {'status': 'uploading'}
    
    def cancel_upload(self, upload_id):
        """
        Cancel async upload
        
        Args:
            upload_id: Upload ID
        
        Returns:
            bool: True if cancelled successfully
        """
        with self.upload_lock:
            if upload_id in self.active_uploads:
                future = self.active_uploads[upload_id]
                cancelled = future.cancel()
                if cancelled:
                    del self.active_uploads[upload_id]
                return cancelled
            return False
    
    def cleanup_failed_uploads(self):
        """Clean up failed upload files"""
        try:
            from app.models import FileUpload
            failed_uploads = FileUpload.query.filter_by(upload_status='failed').all()
            
            for upload in failed_uploads:
                if os.path.exists(upload.file_path):
                    try:
                        os.remove(upload.file_path)
                        logger.info(f"Cleaned up failed upload file: {upload.filename}")
                    except Exception as e:
                        logger.error(f"Failed to clean up file {upload.filename}: {str(e)}")
            
        except Exception as e:
            logger.error(f"Upload cleanup error: {str(e)}")
    
    def get_upload_statistics(self):
        """Get upload statistics"""
        try:
            from app.models import FileUpload
            stats = {
                'total_uploads': FileUpload.query.count(),
                'completed_uploads': FileUpload.query.filter_by(upload_status='completed').count(),
                'failed_uploads': FileUpload.query.filter_by(upload_status='failed').count(),
                'active_uploads': len(self.active_uploads),
                'fixture_uploads': FileUpload.query.filter_by(file_type='fixture').count(),
                'zip_uploads': FileUpload.query.filter_by(file_type='zip').count()
            }
            
            # Calculate total storage used
            completed_uploads = FileUpload.query.filter_by(upload_status='completed').all()
            total_storage = sum(upload.file_size for upload in completed_uploads)
            stats['total_storage_bytes'] = total_storage
            stats['total_storage_mb'] = round(total_storage / (1024 * 1024), 2)
            
            return stats
            
        except Exception as e:
            logger.error(f"Statistics calculation error: {str(e)}")
            return {}

# Global file upload handler instance (lazy initialization)
file_upload_handler = None

def get_file_upload_handler():
    """Get file upload handler instance"""
    global file_upload_handler
    if file_upload_handler is None:
        file_upload_handler = FileUploadHandler()
    return file_upload_handler