# createsonline/static_files.py
"""
CREATESONLINE Dynamic Static File Serving System

Automatically serves HTML, CSS, JS, and other static files
with intelligent MIME type detection and caching.
"""

import os
import mimetypes
from pathlib import Path
from typing import Dict, Optional, Tuple
import logging

logger = logging.getLogger("createsonline.static")

# Initialize mimetypes
mimetypes.init()

# Additional MIME types for modern web
CUSTOM_MIME_TYPES = {
    '.html': 'text/html',
    '.htm': 'text/html',
    '.css': 'text/css',
    '.js': 'application/javascript',
    '.json': 'application/json',
    '.xml': 'application/xml',
    '.svg': 'image/svg+xml',
    '.woff': 'font/woff',
    '.woff2': 'font/woff2',
    '.ttf': 'font/ttf',
    '.eot': 'application/vnd.ms-fontobject',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif': 'image/gif',
    '.ico': 'image/x-icon',
    '.webp': 'image/webp',
    '.mp4': 'video/mp4',
    '.webm': 'video/webm',
    '.mp3': 'audio/mpeg',
    '.wav': 'audio/wav',
    '.pdf': 'application/pdf',
    '.zip': 'application/zip',
    '.txt': 'text/plain',
    '.md': 'text/markdown',
}


class StaticFileHandler:
    """
    Dynamic static file serving with intelligent routing
    """
    
    def __init__(self, static_dirs: list = None, template_dirs: list = None):
        """
        Initialize static file handler
        
        Args:
            static_dirs: List of directories to serve static files from
            template_dirs: List of directories to serve templates/HTML from
        """
        self.static_dirs = static_dirs or []
        self.template_dirs = template_dirs or []
        
        # Default directories if none provided - use current working directory
        if not self.static_dirs and not self.template_dirs:
            cwd = Path.cwd()
            self.static_dirs = [
                cwd / "static",
                cwd / "static" / "css",
                cwd / "static" / "js",
                cwd / "static" / "images",
                cwd / "static" / "icons",
            ]
            self.template_dirs = [
                cwd / "templates",
            ]
        
        # Cache for file existence checks
        self._file_cache = {}
        
        logger.info(f"Static file handler initialized with {len(self.static_dirs)} static dirs")
    
    def get_mime_type(self, file_path: str) -> str:
        """
        Get MIME type for a file with fallback support
        
        Args:
            file_path: Path to the file
            
        Returns:
            MIME type string
        """
        ext = os.path.splitext(file_path)[1].lower()
        
        # Check custom types first
        if ext in CUSTOM_MIME_TYPES:
            return CUSTOM_MIME_TYPES[ext]
        
        # Fall back to system mimetypes
        mime_type, _ = mimetypes.guess_type(file_path)
        
        # Default to octet-stream if unknown
        return mime_type or 'application/octet-stream'
    
    def find_file(self, relative_path: str) -> Optional[Path]:
        """
        Find a file in static or template directories
        
        Args:
            relative_path: Relative path to the file
            
        Returns:
            Absolute Path object if found, None otherwise
        """
        # Check cache first
        if relative_path in self._file_cache:
            cached_path = self._file_cache[relative_path]
            if cached_path and cached_path.exists():
                return cached_path
        
        # Remove leading slash if present
        relative_path = relative_path.lstrip('/')
        
        # Check static directories
        for static_dir in self.static_dirs:
            file_path = Path(static_dir) / relative_path
            if file_path.exists() and file_path.is_file():
                self._file_cache[relative_path] = file_path
                return file_path
        
        # Check template directories
        for template_dir in self.template_dirs:
            file_path = Path(template_dir) / relative_path
            if file_path.exists() and file_path.is_file():
                self._file_cache[relative_path] = file_path
                return file_path
        
        # Cache miss
        self._file_cache[relative_path] = None
        return None
    
    def serve_file(self, relative_path: str) -> Tuple[bytes, int, Dict[str, str]]:
        """
        Serve a static file
        
        Args:
            relative_path: Relative path to the file
            
        Returns:
            Tuple of (content, status_code, headers)
        """
        file_path = self.find_file(relative_path)
        
        if not file_path:
            error_content = f"File not found: {relative_path}".encode()
            return error_content, 404, {'Content-Type': 'text/plain'}
        
        try:
            # Security check - ensure file is within allowed directories
            resolved_path = file_path.resolve()
            allowed = False
            
            for static_dir in self.static_dirs + self.template_dirs:
                if str(resolved_path).startswith(str(Path(static_dir).resolve())):
                    allowed = True
                    break
            
            if not allowed:
                error_content = b"Access denied"
                return error_content, 403, {'Content-Type': 'text/plain'}
            
            # Read file content
            with open(file_path, 'rb') as f:
                content = f.read()
            
            # Get MIME type
            mime_type = self.get_mime_type(str(file_path))
            
            # Prepare headers
            headers = {
                'Content-Type': mime_type,
                'Content-Length': str(len(content)),
                'Cache-Control': 'public, max-age=3600',  # Cache for 1 hour
            }
            
            # Add additional headers for specific file types
            if mime_type.startswith('text/'):
                headers['Content-Type'] = f"{mime_type}; charset=utf-8"
            
            logger.debug(f"Serving file: {relative_path} ({mime_type}, {len(content)} bytes)")
            
            return content, 200, headers
            
        except Exception as e:
            logger.error(f"Error serving file {relative_path}: {e}")
            error_content = f"Error reading file: {str(e)}".encode()
            return error_content, 500, {'Content-Type': 'text/plain'}
    
    def list_files(self, directory: str = "") -> Dict[str, list]:
        """
        List all files in static directories (for debugging)
        
        Args:
            directory: Subdirectory to list (empty for all)
            
        Returns:
            Dictionary with file listings
        """
        files = {
            'static': [],
            'templates': []
        }
        
        # List static files
        for static_dir in self.static_dirs:
            base_dir = Path(static_dir) / directory
            if base_dir.exists():
                for file_path in base_dir.rglob('*'):
                    if file_path.is_file():
                        rel_path = file_path.relative_to(static_dir)
                        files['static'].append(str(rel_path))
        
        # List template files
        for template_dir in self.template_dirs:
            base_dir = Path(template_dir) / directory
            if base_dir.exists():
                for file_path in base_dir.rglob('*'):
                    if file_path.is_file():
                        rel_path = file_path.relative_to(template_dir)
                        files['templates'].append(str(rel_path))
        
        return files


# Global static file handler instance
static_handler = StaticFileHandler()


def serve_static(path: str) -> Tuple[bytes, int, Dict[str, str]]:
    """
    Convenience function to serve static files
    
    Args:
        path: Path to the file
        
    Returns:
        Tuple of (content, status_code, headers)
    """
    return static_handler.serve_file(path)


def serve_template(path: str) -> Tuple[bytes, int, Dict[str, str]]:
    """
    Convenience function to serve HTML templates
    
    Args:
        path: Path to the template
        
    Returns:
        Tuple of (content, status_code, headers)
    """
    # Ensure .html extension
    if not path.endswith('.html'):
        path = f"{path}.html"
    
    return static_handler.serve_file(path)
