"""
Dynamic Product Image Generator
Generates product images with configurable colors, hardware positions, and other options.
Uses PIL/Pillow for image manipulation.
"""

import os
import json
import hashlib
from io import BytesIO
from PIL import Image, ImageDraw, ImageEnhance, ImageColor
from pathlib import Path


class ProductImageGenerator:
    """Handles dynamic generation of product images based on configuration."""
    
    def __init__(self, config_file='data/image_configs.json'):
        """Initialize with configuration file."""
        self.config_file = config_file
        self.configs = self._load_configs()
        self.cache_dir = Path('cache/product_images')
        self.cache_dir.mkdir(parents=True, exist_ok=True)
    
    def _load_configs(self):
        """Load image configuration from JSON file."""
        try:
            with open(self.config_file, 'r') as f:
                return json.load(f)
        except Exception as e:
            print(f"Error loading image configs: {e}")
            return {}
    
    def _find_image(self, image_path):
        """
        Find image file, trying different extensions if needed.
        
        Args:
            image_path: Path to image (e.g., 'images/cobrai.jpg')
            
        Returns:
            Actual path to image file, or None if not found
        """
        # Try the exact path first
        if os.path.exists(image_path):
            return image_path
        
        # Get base path without extension
        base_path = os.path.splitext(image_path)[0]
        
        # Try common image extensions
        for ext in ['.png', '.jpg', '.jpeg', '.PNG', '.JPG', '.JPEG']:
            test_path = base_path + ext
            if os.path.exists(test_path):
                return test_path
        
        return None
    
    def generate_product_image(self, product_code, color='white', hinge='right', 
                              material='aluminum', use_cache=True):
        """
        Generate a product image with specified configuration.
        
        Args:
            product_code: Product identifier (e.g., 'cobrai')
            color: Color name (e.g., 'white', 'black', 'bronze')
            hinge: Hinge side ('right' or 'left')
            material: Material type (for future use)
            use_cache: Whether to use cached images
            
        Returns:
            PIL Image object
        """
        product_code = product_code.lower()
        
        # Check if product config exists
        if product_code not in self.configs:
            raise ValueError(f"Product {product_code} not found in configuration")
        
        config = self.configs[product_code]
        
        # Check cache first
        if use_cache:
            cache_key = self._get_cache_key(product_code, color, hinge, material)
            cached_image = self._get_cached_image(cache_key)
            if cached_image:
                return cached_image
        
        # Load base image
        base_image_path = config['sourceImage']
        
        # Try to find the image with different extensions if needed
        image_path = self._find_image(base_image_path)
        
        if not image_path:
            raise FileNotFoundError(f"Could not load base image: {base_image_path}. Tried .jpg, .png, .jpeg")
        
        try:
            base_image = Image.open(image_path).convert('RGBA')
        except Exception as e:
            raise FileNotFoundError(f"Could not load base image: {image_path}. Error: {e}")
        
        # Apply color to regions
        if color in config['availableColors']:
            color_rgb = tuple(config['availableColors'][color]['rgb'])
            base_image = self._apply_color_to_regions(
                base_image, 
                config['colorableRegions'], 
                color_rgb
            )
        
        # Apply hardware (handles, locks)
        if hinge in config.get('hardwarePositions', {}):
            base_image = self._apply_hardware(
                base_image,
                config['hardwarePositions'],
                hinge
            )
        
        # Flip image if left hinge
        if hinge == 'left':
            base_image = base_image.transpose(Image.FLIP_LEFT_RIGHT)
        
        # Cache the result
        if use_cache:
            self._cache_image(cache_key, base_image)
        
        return base_image
    
    def _apply_color_to_regions(self, image, regions, target_color):
        """
        Apply color to specific regions of the image.
        
        This method attempts to recolor the frame/panel areas while preserving
        shadows, highlights, and texture.
        """
        img_copy = image.copy()
        pixels = img_copy.load()
        width, height = img_copy.size
        
        # Get the average brightness of the target color
        target_brightness = sum(target_color) / 3
        
        for region in regions:
            x1, y1 = region['topLeft']
            x2, y2 = region['bottomRight']
            
            # Ensure coordinates are within bounds
            x1 = max(0, min(x1, width - 1))
            x2 = max(0, min(x2, width))
            y1 = max(0, min(y1, height - 1))
            y2 = max(0, min(y2, height))
            
            # Apply color to region while preserving luminosity
            for y in range(y1, y2):
                for x in range(x1, x2):
                    try:
                        r, g, b, a = pixels[x, y]
                        
                        # Calculate original brightness
                        original_brightness = (r + g + b) / 3
                        
                        # Preserve relative brightness
                        if original_brightness > 0:
                            brightness_factor = original_brightness / 255.0
                            
                            # Apply target color with brightness preservation
                            new_r = int(target_color[0] * brightness_factor)
                            new_g = int(target_color[1] * brightness_factor)
                            new_b = int(target_color[2] * brightness_factor)
                            
                            # Ensure values are in valid range
                            new_r = max(0, min(255, new_r))
                            new_g = max(0, min(255, new_g))
                            new_b = max(0, min(255, new_b))
                            
                            pixels[x, y] = (new_r, new_g, new_b, a)
                    except IndexError:
                        continue
        
        return img_copy
    
    def _apply_hardware(self, image, hardware_positions, hinge_side):
        """Apply hardware (handles, locks) to the image."""
        img_copy = image.copy()
        
        hardware_key = f"handle_{hinge_side}"
        if hardware_key in hardware_positions:
            hardware_config = hardware_positions[hardware_key]
            hardware_path = hardware_config.get('image')
            
            if hardware_path:
                # Try to find the hardware image with different extensions
                actual_path = self._find_image(hardware_path)
                
                if actual_path:
                    try:
                        hardware_img = Image.open(actual_path).convert('RGBA')
                        
                        # Flip if needed
                        if hardware_config.get('flip', False):
                            hardware_img = hardware_img.transpose(Image.FLIP_LEFT_RIGHT)
                        
                        # Paste hardware at specified position
                        x, y = hardware_config['x'], hardware_config['y']
                        img_copy.paste(hardware_img, (x, y), hardware_img)
                    except Exception as e:
                        print(f"Could not apply hardware: {e}")
        
        return img_copy
    
    def _get_cache_key(self, product_code, color, hinge, material):
        """Generate a unique cache key for the configuration."""
        key_string = f"{product_code}_{color}_{hinge}_{material}"
        return hashlib.md5(key_string.encode()).hexdigest()
    
    def _get_cached_image(self, cache_key):
        """Retrieve cached image if available."""
        cache_path = self.cache_dir / f"{cache_key}.png"
        if cache_path.exists():
            try:
                return Image.open(cache_path)
            except Exception:
                return None
        return None
    
    def _cache_image(self, cache_key, image):
        """Save image to cache."""
        cache_path = self.cache_dir / f"{cache_key}.png"
        try:
            image.save(cache_path, 'PNG', optimize=True)
        except Exception as e:
            print(f"Could not cache image: {e}")
    
    def get_product_config(self, product_code):
        """Get configuration for a specific product."""
        return self.configs.get(product_code.lower())
    
    def clear_cache(self, product_code=None):
        """Clear cached images. If product_code provided, only clear that product's cache."""
        if product_code:
            # Clear specific product cache (would need to track cache keys)
            pass
        else:
            # Clear all cache
            for cache_file in self.cache_dir.glob('*.png'):
                try:
                    cache_file.unlink()
                except Exception:
                    pass


def recolor_image_advanced(image, source_color_range, target_color):
    """
    Advanced recoloring that replaces pixels within a color range.
    
    Args:
        image: PIL Image
        source_color_range: Dict with 'min' and 'max' RGB tuples
        target_color: Target RGB tuple
    """
    img_copy = image.copy()
    pixels = img_copy.load()
    width, height = img_copy.size
    
    min_r, min_g, min_b = source_color_range['min']
    max_r, max_g, max_b = source_color_range['max']
    
    for y in range(height):
        for x in range(width):
            r, g, b, a = pixels[x, y]
            
            # Check if pixel is within source color range
            if (min_r <= r <= max_r and 
                min_g <= g <= max_g and 
                min_b <= b <= max_b):
                
                # Calculate brightness factor
                brightness = (r + g + b) / 3 / 255.0
                
                # Apply target color with brightness
                new_r = int(target_color[0] * brightness)
                new_g = int(target_color[1] * brightness)
                new_b = int(target_color[2] * brightness)
                
                pixels[x, y] = (new_r, new_g, new_b, a)
    
    return img_copy


def image_to_base64(image, format='PNG'):
    """Convert PIL Image to base64 string."""
    import base64
    buffered = BytesIO()
    image.save(buffered, format=format)
    img_str = base64.b64encode(buffered.getvalue()).decode()
    return f"data:image/{format.lower()};base64,{img_str}"
