import base64
import io
import logging

import requests
from mcp.types import ImageContent, TextContent
from PIL import Image

logger = logging.getLogger(__name__)


class ImageContentMaker:
    """Controller for image processing and optimization for MCP."""

    MAX_SIZE_BYTES = 0.7 * 1024 * 1024  # 0.7MB limit
    MIN_QUALITY = 10
    MAX_QUALITY = 95
    DEFAULT_QUALITY = 85
    JPEG_MIME_TYPE = "image/jpeg"

    def __init__(self, image_url: str):
        self.image_bytes = self.download_image(image_url)

    @staticmethod
    def download_image(image_url: str) -> bytes:
        with requests.get(image_url, stream=True, timeout=10) as response:
            response.raise_for_status()
            chunks = [chunk for chunk in response.iter_content(chunk_size=8192) if chunk]
            img_bytes = b"".join(chunks)
        return img_bytes

    def to_viewable_content(self) -> ImageContent | TextContent:
        if len(self.image_bytes) <= self.MAX_SIZE_BYTES:
            return self._get_image_content(self.image_bytes)

        optimized_bytes = self._find_optimal_compression(self.image_bytes)

        if optimized_bytes is None:
            return TextContent(type="text", text="Image is too large, open image url for preview")

        return self._get_image_content(optimized_bytes)

    def _get_image_content(self, image_bytes: bytes) -> ImageContent:
        return ImageContent(type="image", data=base64.b64encode(image_bytes).decode(), mimeType=self.JPEG_MIME_TYPE)

    def _find_optimal_compression(self, image_bytes: bytes) -> bytes | None:
        """
        Use binary search to find the highest quality that fits within size limit.

        Args:
            image_bytes: Original image bytes

        Returns:
            Optimized JPEG bytes, or None if compression failed
        """
        min_quality = self.MIN_QUALITY
        max_quality = self.MAX_QUALITY
        best_result = None

        while min_quality <= max_quality:
            quality = (min_quality + max_quality) // 2

            try:
                compressed = self._convert_to_jpeg(image_bytes, quality=quality)

                if len(compressed) <= self.MAX_SIZE_BYTES:
                    best_result = compressed
                    min_quality = quality + 1  # Try higher quality
                else:
                    max_quality = quality - 1  # Reduce quality

            except Exception as e:
                logger.warning(f"Compression failed at quality {quality}: {e}")
                max_quality = quality - 1

        # Fallback to minimum quality if binary search failed
        if best_result is None:
            try:
                logger.info("Binary search failed, trying minimum quality")
                best_result = self._convert_to_jpeg(image_bytes, quality=self.MIN_QUALITY)
            except Exception as e:
                logger.error(f"Minimum quality compression failed: {e}")

        if (best_result is None) or (len(best_result) > self.MAX_SIZE_BYTES):
            return None

        return best_result

    @staticmethod
    def _convert_to_jpeg(image_bytes: bytes, quality: int = 85) -> bytes:
        """
        Convert image bytes to optimized JPEG format.

        Args:
            image_bytes: Raw image data
            quality: JPEG quality (1-95)

        Returns:
            JPEG-encoded bytes

        Raises:
            RuntimeError: If JPEG support is not available
            Exception: If conversion fails
        """
        img = Image.open(io.BytesIO(image_bytes))
        logger.debug(f"Converting {img.format} image", extra={"mode": img.mode, "size": img.size})

        # Convert to RGB if needed (handle transparency)
        if img.mode not in ("RGB", "L"):
            if img.mode == "RGBA" or (img.mode == "P" and "transparency" in img.info):
                # Create white background for transparency
                background = Image.new("RGB", img.size, (255, 255, 255))
                if img.mode != "RGBA":
                    img = img.convert("RGBA")
                background.paste(img, mask=img.split()[-1])
                img = background
            else:
                img = img.convert("RGB")

        output = io.BytesIO()
        img.save(output, format="JPEG", quality=quality, optimize=True, progressive=True, subsampling="4:2:0")
        return output.getvalue()
