"""
Image processing engine for Capture.
Implements enhancement features: sharpen, highlight, and upscale.
"""
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
from pathlib import Path
from typing import Tuple, Optional, List


class ImageProcessor:
    """Handles image enhancement operations."""
    
    @staticmethod
    def calculate_sharpness_score(image_array: np.ndarray) -> float:
        """
        Laplacian Sharpness Scorer: Measures edge density locally.
        Returns the variance of the Laplacian.
        """
        try:
            gray = cv2.cvtColor(image_array, cv2.COLOR_BGR2GRAY)
            return cv2.Laplacian(gray, cv2.CV_64F).var()
        except Exception as e:
            print(f"Error calculating sharpness: {e}")
            return 0.0

    @staticmethod
    def smart_optimize(image_array: np.ndarray) -> Tuple[int, int]:
        """
        Smart Optimize: Uses histogram analysis to recommend optimal brightness and contrast.
        Returns recommended (brightness, contrast) slider values.
        """
        try:
            gray = cv2.cvtColor(image_array, cv2.COLOR_BGR2GRAY)
            # Find 1st and 99th percentiles
            min_val = np.percentile(gray, 1)
            max_val = np.percentile(gray, 99)
            
            # Target range is 0-255. We want to stretch [min_val, max_val] to [0, 255]
            if max_val <= min_val:
                return (0, 0)
            
            # Convert stretch back to slider values
            # Contrast slider (-100 to 100) -> alpha (0.5 to 3.0)
            alpha_needed = 255.0 / (max_val - min_val)
            alpha_needed = max(0.5, min(alpha_needed, 3.0)) # Clamp
            
            contrast_slider = int(((alpha_needed - 1.0) / 2.0) * 100)
            
            # Brightness slider (-100 to 100) -> beta
            # To anchor the min_val at 0: 0 = alpha_needed * min_val + beta -> beta = -alpha_needed * min_val
            beta_needed = -alpha_needed * min_val
            
            brightness_slider = int(max(-100, min(100, beta_needed)))
            
            return (brightness_slider, contrast_slider)
        except Exception as e:
            print(f"Error in smart optimize: {e}")
            return (0, 0)
    
    @staticmethod
    def sharpen_image(image_path: Path, strength: float = 1.5) -> Optional[np.ndarray]:
        """
        Apply unsharp mask filter to improve text legibility.
        
        Args:
            image_path: Path to source image
            strength: Sharpening strength (1.0-3.0 recommended)
            
        Returns:
            Sharpened image as numpy array or None
        """
        try:
            # Read image
            img = cv2.imread(str(image_path))
            if img is None:
                return None
            
            # Apply Gaussian blur
            gaussian = cv2.GaussianBlur(img, (0, 0), 2.0)
            
            # Unsharp mask: original + (original - blurred) * strength
            sharpened = cv2.addWeighted(img, 1.0 + strength, gaussian, -strength, 0)
            
            return sharpened
        except Exception as e:
            print(f"Error sharpening image: {e}")
            return None
    
    @staticmethod
    def add_highlight(
        image_array: np.ndarray,
        x: int,
        y: int,
        width: int,
        height: int,
        color: Tuple[int, int, int] = (255, 255, 0),
        opacity: float = 0.3
    ) -> np.ndarray:
        """
        Add semi-transparent highlight rectangle to image.
        
        Args:
            image_array: Image as numpy array
            x, y: Top-left corner coordinates
            width, height: Rectangle dimensions
            color: RGB color tuple
            opacity: Transparency (0.0-1.0)
            
        Returns:
            Image with highlight overlay
        """
        try:
            # Create overlay
            overlay = image_array.copy()
            
            # BGR color for OpenCV
            bgr_color = (color[2], color[1], color[0])
            
            # Draw filled rectangle
            cv2.rectangle(overlay, (x, y), (x + width, y + height), bgr_color, -1)
            
            # Blend with original
            result = cv2.addWeighted(overlay, opacity, image_array, 1 - opacity, 0)
            
            return result
        except Exception as e:
            print(f"Error adding highlight: {e}")
            return image_array
    
    @staticmethod
    def add_border_highlight(
        image_array: np.ndarray,
        x: int,
        y: int,
        width: int,
        height: int,
        color: Tuple[int, int, int] = (255, 0, 0),
        thickness: int = 3
    ) -> np.ndarray:
        """
        Add border highlight to image (no fill).
        
        Args:
            image_array: Image as numpy array
            x, y: Top-left corner coordinates
            width, height: Rectangle dimensions
            color: RGB color tuple
            thickness: Border thickness in pixels
            
        Returns:
            Image with border highlight
        """
        try:
            result = image_array.copy()
            bgr_color = (color[2], color[1], color[0])
            cv2.rectangle(result, (x, y), (x + width, y + height), bgr_color, thickness)
            return result
        except Exception as e:
            print(f"Error adding border: {e}")
            return image_array
    
    @staticmethod
    def upscale_placeholder(image_path: Path, scale_factor: int = 2) -> Optional[np.ndarray]:
        """
        Placeholder for AI-based upscaling (currently uses bicubic).
        Future: Integrate Real-ESRGAN or similar.
        
        Args:
            image_path: Path to source image
            scale_factor: Upscaling factor
            
        Returns:
            Upscaled image or None
        """
        try:
            img = cv2.imread(str(image_path))
            if img is None:
                return None
            
            height, width = img.shape[:2]
            new_dims = (width * scale_factor, height * scale_factor)
            
            # Using bicubic interpolation (placeholder for AI model)
            upscaled = cv2.resize(img, new_dims, interpolation=cv2.INTER_CUBIC)
            
            return upscaled
        except Exception as e:
            print(f"Error upscaling image: {e}")
            return None
    
    @staticmethod
    def save_image(image_array: np.ndarray, output_path: Path) -> bool:
        """
        Save processed image to disk.
        
        Args:
            image_array: Image as numpy array
            output_path: Destination path
            
        Returns:
            True if successful, False otherwise
        """
        try:
            cv2.imwrite(str(output_path), image_array)
            return True
        except Exception as e:
            print(f"Error saving image: {e}")
            return False
    
    @staticmethod
    def array_to_pillow(image_array: np.ndarray) -> Image.Image:
        """
        Convert OpenCV numpy array to PIL Image.
        
        Args:
            image_array: OpenCV BGR image
            
        Returns:
            PIL Image in RGB
        """
        # Convert BGR to RGB
        rgb = cv2.cvtColor(image_array, cv2.COLOR_BGR2RGB)
        return Image.fromarray(rgb)
    
    @staticmethod
    def pillow_to_array(pil_image: Image.Image) -> np.ndarray:
        """
        Convert PIL Image to OpenCV numpy array.
        
        Args:
            pil_image: PIL Image
            
        Returns:
            OpenCV BGR array
        """
        rgb = np.array(pil_image)
        return cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)

    @staticmethod
    def apply_manual_adjustments(
        image_array: np.ndarray,
        brightness: int = 0,
        contrast: int = 0,
        saturation: int = 0,
        sharpness: int = 0
    ) -> np.ndarray:
        """
        Apply manual adjustments using slider values.
        
        Args:
            image_array: Original image as numpy array
            brightness: Brightness adjustment (-100 to +100)
            contrast: Contrast adjustment (-100 to +100)
            saturation: Saturation adjustment (-100 to +100)
            sharpness: Sharpness adjustment (0 to +100)
            
        Returns:
            Adjusted image as numpy array
        """
        try:
            result = image_array.copy()
            
            # 1. Apply Brightness & Contrast
            # Map slider range to OpenCV parameters
            # Contrast: -100 to +100 -> alpha: 0.5 to 3.0
            # Brightness: -100 to +100 -> beta: -100 to +100
            if brightness != 0 or contrast != 0:
                # Calculate alpha (contrast multiplier)
                alpha = 1.0 + (contrast / 100.0) * 2.0  # Range: 0.5 to 3.0
                alpha = max(0.1, min(alpha, 5.0))  # Clamp for safety
                
                # Beta is brightness offset
                beta = brightness
                
                result = cv2.convertScaleAbs(result, alpha=alpha, beta=beta)
            
            # 2. Apply Saturation
            if saturation != 0:
                # Convert to HSV color space
                hsv = cv2.cvtColor(result, cv2.COLOR_BGR2HSV).astype(np.float32)
                
                # Scale the S (saturation) channel
                # Map -100 to +100 -> 0.0 to 2.0 multiplier
                sat_scale = 1.0 + (saturation / 100.0)
                sat_scale = max(0.0, min(sat_scale, 3.0))  # Clamp
                
                hsv[:, :, 1] = hsv[:, :, 1] * sat_scale
                hsv[:, :, 1] = np.clip(hsv[:, :, 1], 0, 255)
                
                # Convert back to BGR
                result = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
            
            # 3. Apply Sharpness (Unsharp Mask)
            if sharpness > 0:
                # Map 0-100 to strength 0.0-2.0
                strength = (sharpness / 100.0) * 2.0
                
                # Apply Gaussian blur
                gaussian = cv2.GaussianBlur(result, (0, 0), 2.0)
                
                # Unsharp mask: original + (original - blurred) * strength
                result = cv2.addWeighted(result, 1.0 + strength, gaussian, -strength, 0)
            
            return result
            
        except Exception as e:
            print(f"Error applying manual adjustments: {e}")
            return image_array
