"""
File upload security utilities for ivatar
"""

import hashlib
import logging
import magic
import os
from io import BytesIO
from typing import Dict, Tuple

from PIL import Image

# Initialize logger
logger = logging.getLogger("ivatar.security")

# Security constants
ALLOWED_MIME_TYPES = [
    "image/jpeg",
    "image/png",
    "image/gif",
    "image/webp",
    "image/bmp",
    "image/tiff",
]

ALLOWED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff"]

# Magic byte signatures for image formats
IMAGE_SIGNATURES = {
    b"\xff\xd8\xff": "image/jpeg",
    b"\x89PNG\r\n\x1a\n": "image/png",
    b"GIF87a": "image/gif",
    b"GIF89a": "image/gif",
    b"RIFF": "image/webp",  # WebP starts with RIFF
    b"BM": "image/bmp",
    b"II*\x00": "image/tiff",  # Little-endian TIFF
    b"MM\x00*": "image/tiff",  # Big-endian TIFF
}

# Maximum file size for different operations (in bytes)
MAX_FILE_SIZE_BASIC = 5 * 1024 * 1024  # 5MB for basic validation
MAX_FILE_SIZE_SCAN = 10 * 1024 * 1024  # 10MB for virus scanning
MAX_FILE_SIZE_PROCESS = 50 * 1024 * 1024  # 50MB for processing


class FileUploadSecurityError(Exception):
    """Custom exception for file upload security issues"""

    pass


class FileValidator:
    """Comprehensive file validation for uploads"""

    def __init__(self, file_data: bytes, filename: str):
        self.file_data = file_data
        self.filename = filename
        self.file_size = len(file_data)
        self.file_hash = hashlib.sha256(file_data).hexdigest()

    def validate_basic(self) -> Dict[str, any]:
        """
        Perform basic file validation
        Returns validation results dictionary
        """
        results = {
            "valid": True,
            "errors": [],
            "warnings": [],
            "file_info": {
                "size": self.file_size,
                "hash": self.file_hash,
                "filename": self.filename,
            },
        }

        # Check file size
        if self.file_size > MAX_FILE_SIZE_BASIC:
            results["valid"] = False
            results["errors"].append(f"File too large: {self.file_size} bytes")

        # Check filename
        if not self.filename or len(self.filename) > 255:
            results["valid"] = False
            results["errors"].append("Invalid filename")

        # Check file extension
        ext = os.path.splitext(self.filename)[1].lower()
        if ext not in ALLOWED_EXTENSIONS:
            results["valid"] = False
            results["errors"].append(f"File extension not allowed: {ext}")

        return results

    def validate_magic_bytes(self) -> Dict[str, any]:
        """
        Validate file using magic bytes (file signatures)
        """
        results = {"valid": True, "detected_type": None, "errors": []}

        # Check magic bytes
        detected_type = None
        for signature, mime_type in IMAGE_SIGNATURES.items():
            if self.file_data.startswith(signature):
                detected_type = mime_type
                break

        # Special handling for WebP (RIFF + WEBP)
        if self.file_data.startswith(b"RIFF") and b"WEBP" in self.file_data[:12]:
            detected_type = "image/webp"

        if not detected_type:
            results["valid"] = False
            results["errors"].append(
                "File signature does not match any supported image format"
            )
        else:
            results["detected_type"] = detected_type

        return results

    def validate_mime_type(self) -> Dict[str, any]:
        """
        Validate MIME type using python-magic
        """
        results = {"valid": True, "detected_mime": None, "errors": []}

        try:
            # Use python-magic to detect MIME type
            detected_mime = magic.from_buffer(self.file_data, mime=True)
            results["detected_mime"] = detected_mime

            if detected_mime not in ALLOWED_MIME_TYPES:
                results["valid"] = False
                results["errors"].append(f"MIME type not allowed: {detected_mime}")

        except Exception as e:
            logger.warning(f"MIME type detection failed: {e}")
            results["warnings"].append("Could not detect MIME type")

        return results

    def validate_pil_image(self) -> Dict[str, any]:
        """
        Validate using PIL to ensure it's a valid image
        """
        results = {"valid": True, "image_info": {}, "errors": []}

        try:
            # Open image with PIL
            image = Image.open(BytesIO(self.file_data))

            # Get image information
            results["image_info"] = {
                "format": image.format,
                "mode": image.mode,
                "size": image.size,
                "width": image.width,
                "height": image.height,
                "has_transparency": image.mode in ("RGBA", "LA", "P"),
            }

            # Verify image can be loaded
            image.load()

            # Check for suspicious characteristics
            if image.width > 10000 or image.height > 10000:
                results["warnings"].append("Image dimensions are very large")

            if image.width < 1 or image.height < 1:
                results["valid"] = False
                results["errors"].append("Invalid image dimensions")

        except Exception as e:
            results["valid"] = False
            results["errors"].append(f"Invalid image format: {str(e)}")

        return results

    def sanitize_exif_data(self) -> bytes:
        """
        Remove EXIF data from image to prevent metadata leaks
        """
        try:
            image = Image.open(BytesIO(self.file_data))

            # Create new image without EXIF data
            if image.mode in ("RGBA", "LA"):
                # Preserve transparency
                new_image = Image.new("RGBA", image.size, (255, 255, 255, 0))
                new_image.paste(image, mask=image.split()[-1])
            else:
                new_image = Image.new("RGB", image.size, (255, 255, 255))
                new_image.paste(image)

            # Save without EXIF data
            output = BytesIO()
            new_image.save(output, format=image.format or "JPEG", quality=95)
            return output.getvalue()

        except Exception as e:
            logger.warning(f"EXIF sanitization failed: {e}")
            return self.file_data  # Return original if sanitization fails

    def scan_for_malicious_content(self) -> Dict[str, any]:
        """
        Scan for potentially malicious content patterns
        """
        results = {"suspicious": False, "threats": [], "warnings": []}

        # Check for embedded scripts or executable content
        suspicious_patterns = [
            b"<script",
            b"javascript:",
            b"vbscript:",
            b"data:text/html",
            b"<?php",
            b"<%",
            b"#!/bin/",
            b"MZ",  # PE executable header
            b"\x7fELF",  # ELF executable header
        ]

        for pattern in suspicious_patterns:
            if pattern in self.file_data:
                results["suspicious"] = True
                results["threats"].append(f"Suspicious pattern detected: {pattern}")

        # Check for polyglot files (valid in multiple formats)
        if self.file_data.startswith(b"GIF89a") and b"<script" in self.file_data:
            results["suspicious"] = True
            results["threats"].append("Potential polyglot attack detected")

        return results

    def comprehensive_validation(self) -> Dict[str, any]:
        """
        Perform comprehensive file validation
        """
        results = {
            "valid": True,
            "errors": [],
            "warnings": [],
            "file_info": {},
            "security_score": 100,
        }

        # Basic validation
        basic_results = self.validate_basic()
        if not basic_results["valid"]:
            results["valid"] = False
            results["errors"].extend(basic_results["errors"])
            results["security_score"] -= 30

        results["file_info"].update(basic_results["file_info"])
        results["warnings"].extend(basic_results["warnings"])

        # Magic bytes validation
        magic_results = self.validate_magic_bytes()
        if not magic_results["valid"]:
            results["valid"] = False
            results["errors"].extend(magic_results["errors"])
            results[
                "security_score"
            ] -= 10  # Reduced from 25 - basic format issue, not security threat

        results["file_info"]["detected_type"] = magic_results["detected_type"]

        # MIME type validation
        mime_results = self.validate_mime_type()
        if not mime_results["valid"]:
            results["valid"] = False
            results["errors"].extend(mime_results["errors"])
            results[
                "security_score"
            ] -= 10  # Reduced from 20 - basic format issue, not security threat

        results["file_info"]["detected_mime"] = mime_results["detected_mime"]
        results["warnings"].extend(mime_results.get("warnings", []))

        # PIL image validation
        pil_results = self.validate_pil_image()
        if not pil_results["valid"]:
            results["valid"] = False
            results["errors"].extend(pil_results["errors"])
            results[
                "security_score"
            ] -= 10  # Reduced from 15 - basic format issue, not security threat

        results["file_info"]["image_info"] = pil_results["image_info"]
        results["warnings"].extend(pil_results.get("warnings", []))

        # Security scan
        security_results = self.scan_for_malicious_content()
        if security_results["suspicious"]:
            results["valid"] = False
            results["errors"].extend(security_results["threats"])
            results["security_score"] -= 50

        results["warnings"].extend(security_results.get("warnings", []))

        # Log security events
        if not results["valid"]:
            logger.warning(f"File upload validation failed: {results['errors']}")
        elif results["security_score"] < 80:
            logger.info(
                f"File upload with low security score: {results['security_score']}"
            )

        return results


def validate_uploaded_file(
    file_data: bytes, filename: str
) -> Tuple[bool, Dict[str, any], bytes]:
    """
    Main function to validate uploaded files

    Returns:
        (is_valid, validation_results, sanitized_data)
    """
    validator = FileValidator(file_data, filename)

    # Perform comprehensive validation
    results = validator.comprehensive_validation()

    if not results["valid"]:
        return False, results, file_data

    # Sanitize EXIF data
    sanitized_data = validator.sanitize_exif_data()

    return True, results, sanitized_data


def get_file_security_report(file_data: bytes, filename: str) -> Dict[str, any]:
    """
    Generate a security report for a file without modifying it
    """
    validator = FileValidator(file_data, filename)
    return validator.comprehensive_validation()
