"""Gemini i2i 앵글/색감 편집 모듈 — 원본 이미지 기반 변형 생성."""

import base64
import io
import json
import logging
import math
import socket
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Optional

from PIL import Image, ImageDraw

logger = logging.getLogger(__name__)

GEMINI_API_URL_TEMPLATE = (
    "https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}"
)
from app.core.config import settings as _settings

I2I_PROMPT_DIR = (
    Path(__file__).resolve().parent.parent.parent.parent
    / "prompts" / "_base" / "i2i_editor" / "v1"
)

def _load_i2i_prompt(filename: str) -> str:
    return (I2I_PROMPT_DIR / filename).read_text(encoding="utf-8").strip()


def _create_camera_diagram(
    horizontal_deg: int,
    vertical_deg: int = 0,
    zoom: float = 1.0,
    size: int = 512,
) -> bytes:
    """카메라 앵글 다이어그램 이미지 생성 (PIL).

    Args:
        horizontal_deg: 수평 회전 각도 (0=front, 90=right, 180=behind, 270=left).
        vertical_deg: 수직 각도 (-30~30, positive=looking down, negative=looking up).
        zoom: 줌 배율 (0.8~1.5, 1.0=normal).
        size: 이미지 크기 (px).

    Returns:
        PNG image bytes.
    """
    img = Image.new("RGB", (size, size), (30, 30, 40))
    draw = ImageDraw.Draw(img)

    cx, cy = size // 2, size // 2
    r = size // 3

    # Draw the circle
    draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=(200, 200, 200), width=2)

    # Degree labels
    draw.text((cx + r + 10, cy - 10), "0\u00b0", fill=(255, 100, 100))
    draw.text((cx - 15, cy + r + 10), "90\u00b0", fill=(255, 100, 100))

    # IMAGE label (center)
    draw.rectangle(
        [cx - 30, cy - 8, cx + 30, cy + 8],
        fill=(80, 80, 80),
        outline=(200, 200, 200),
    )
    draw.text((cx - 20, cy - 6), "IMAGE", fill=(255, 255, 255))

    # Camera position
    rad = math.radians(horizontal_deg)
    cam_x = cx + int(r * 0.7 * math.cos(rad))
    cam_y = cy + int(r * 0.7 * math.sin(rad))

    # Camera -> center arrow
    draw.line([(cam_x, cam_y), (cx, cy)], fill=(255, 200, 0), width=3)

    # Camera icon
    draw.rectangle(
        [cam_x - 12, cam_y - 10, cam_x + 12, cam_y + 10],
        fill=(180, 180, 180),
        outline=(255, 255, 255),
    )
    draw.text((cam_x - 25, cam_y + 15), "CAMERA", fill=(200, 200, 200))

    # Angle text
    draw.text((10, 10), f"Angle: {horizontal_deg}\u00b0", fill=(100, 200, 255))
    if vertical_deg != 0:
        draw.text((10, 30), f"Elevation: {vertical_deg}\u00b0", fill=(100, 200, 255))
    if zoom != 1.0:
        draw.text((10, 50), f"Zoom: {zoom:.1f}x", fill=(100, 200, 255))

    buf = io.BytesIO()
    img.save(buf, format="PNG")
    return buf.getvalue()


def _gemini_generate_content(
    api_key: str,
    model: str,
    prompt: str,
    input_images: list,
    aspect_ratio: str = "16:9",
) -> bytes:
    """Gemini generateContent API 호출 — i2i 편집.

    Args:
        api_key: Gemini API key.
        model: Gemini model name.
        prompt: Text prompt for editing.
        input_images: List of (label, image_bytes) tuples.
        aspect_ratio: Aspect ratio for the output.

    Returns:
        Edited image bytes (PNG).

    Raises:
        RuntimeError: If the API call fails or no image is returned.
    """
    parts = [{"text": prompt}]

    for label, img_bytes in input_images:
        if label:
            parts.append({"text": label})
        parts.append({
            "inline_data": {
                "mime_type": "image/png",
                "data": base64.b64encode(img_bytes).decode("ascii"),
            }
        })

    body = {
        "contents": [{"parts": parts}],
        "generationConfig": {
            "responseModalities": ["TEXT", "IMAGE"],
            "imageConfig": {
                "aspectRatio": aspect_ratio,
                "imageSize": "2K",
            },
        },
    }

    url = GEMINI_API_URL_TEMPLATE.format(model=model, api_key=api_key)
    req = urllib.request.Request(
        url,
        data=json.dumps(body).encode("utf-8"),
        headers={"Content-Type": "application/json"},
        method="POST",
    )

    timeout = _settings.llm_timeout_image_gen
    max_retries = _settings.llm_max_retries
    last_error: Exception | None = None
    for attempt in range(1, max_retries + 2):  # 첫 시도 + max_retries 재시도
        try:
            with urllib.request.urlopen(req, timeout=timeout) as response:
                payload = json.loads(response.read().decode("utf-8"))
            break
        except urllib.error.HTTPError as exc:
            error_text = exc.read().decode("utf-8", errors="replace")
            last_error = RuntimeError(f"Gemini i2i API error {exc.code}: {error_text}")
            if exc.code in {429, 500, 502, 503, 504} and attempt <= max_retries:
                time.sleep(2 * attempt)
                continue
            raise last_error from exc
        except (urllib.error.URLError, socket.timeout) as exc:
            last_error = exc
            if attempt <= max_retries:
                time.sleep(2 * attempt)
                continue
            raise RuntimeError(f"Gemini i2i API failed after retries: {exc}") from exc
    else:
        raise RuntimeError(f"Gemini i2i API failed: {last_error}")

    # Check for moderation blocks
    prompt_feedback = payload.get("promptFeedback", {})
    block_reason = prompt_feedback.get("blockReason")
    if block_reason:
        raise RuntimeError(f"Content moderation blocked: {block_reason}")

    candidates = payload.get("candidates", [])
    if candidates:
        finish_reason = candidates[0].get("finishReason", "")
        if finish_reason == "SAFETY":
            raise RuntimeError("Content blocked by safety filter")

    # Extract image bytes
    for candidate in candidates:
        content = candidate.get("content", {})
        for part in content.get("parts", []):
            inline_data = part.get("inlineData") or part.get("inline_data")
            if isinstance(inline_data, dict):
                b64 = inline_data.get("data")
                if b64:
                    return base64.b64decode(b64)

    raise RuntimeError(f"Gemini i2i returned no image: {json.dumps(payload)[:500]}")


class GeminiI2IEditor:
    """Gemini i2i로 앵글/색감 편집.

    원본 이미지를 받아 카메라 앵글 변경, 색감/조명 변경, 또는
    둘 다 적용한 변형 이미지를 생성한다.
    """

    def __init__(self, api_key: str, model: str) -> None:
        self._api_key = api_key
        self._model = model

    def edit_angle(
        self,
        image_bytes: bytes,
        horizontal: int,
        vertical: int,
        zoom: float,
        prompt: str = "",
    ) -> bytes:
        """카메라 다이어그램 + 프롬프트로 앵글 변경.

        Args:
            image_bytes: Original image PNG bytes.
            horizontal: Horizontal rotation degrees (0-360).
            vertical: Vertical angle degrees (-30 to 30).
            zoom: Zoom factor (0.8 to 1.5).
            prompt: Optional additional prompt for the edit.

        Returns:
            Edited image bytes (PNG).
        """
        diagram_bytes = _create_camera_diagram(horizontal, vertical, zoom)

        # 앵글은 다이어그램으로만 전달 — 텍스트에는 렌즈/구도 정보만
        angle_prompt = _load_i2i_prompt("angle.md")
        if prompt:
            angle_prompt += " " + _load_i2i_prompt("composition.md").format(composition=prompt)

        return _gemini_generate_content(
            api_key=self._api_key,
            model=self._model,
            prompt=angle_prompt,
            input_images=[
                ("Original scene image:", image_bytes),
                ("Camera angle diagram:", diagram_bytes),
            ],
        )

    def edit_color(
        self,
        image_bytes: bytes,
        color_prompt: str,
    ) -> bytes:
        """색감/조명 변경.

        Args:
            image_bytes: Original image PNG bytes.
            color_prompt: Description of the desired lighting/color change.

        Returns:
            Edited image bytes (PNG).
        """
        full_prompt = _load_i2i_prompt("color.md").format(color_prompt=color_prompt)

        return _gemini_generate_content(
            api_key=self._api_key,
            model=self._model,
            prompt=full_prompt,
            input_images=[
                ("Original scene image:", image_bytes),
            ],
        )

    def edit_combined(
        self,
        original_bytes: bytes,
        horizontal: int,
        vertical: int,
        zoom: float,
        color_prompt: str,
        composition: str = "",
    ) -> bytes:
        """Apply angle + color in a single I2I call.

        Creates a camera angle diagram AND includes color prompt in a single
        generation request. Falls back to sequential (angle then color) if
        single call fails.

        Args:
            original_bytes: Original image PNG bytes.
            horizontal: Horizontal rotation degrees (0-360).
            vertical: Vertical angle degrees (-30 to 30).
            zoom: Zoom factor (0.8 to 1.5).
            color_prompt: Color/lighting change description.
            composition: Optional composition note.

        Returns:
            Edited image bytes (PNG) with both angle and color applied.
        """
        diagram_bytes = _create_camera_diagram(horizontal, vertical, zoom)

        combined_prompt = _load_i2i_prompt("combined.md").format(
            color_prompt=color_prompt,
        )
        if composition:
            combined_prompt += " " + _load_i2i_prompt("composition.md").format(
                composition=composition,
            )

        try:
            return _gemini_generate_content(
                api_key=self._api_key,
                model=self._model,
                prompt=combined_prompt,
                input_images=[
                    ("Original scene image:", original_bytes),
                    ("Camera angle diagram:", diagram_bytes),
                ],
            )
        except Exception as exc:
            logger.warning(
                "edit_combined single-call failed (%s), falling back to sequential",
                exc,
            )
            angle_result = self.edit_angle(
                original_bytes, horizontal, vertical, zoom, prompt=composition,
            )
            return self.edit_color(angle_result, color_prompt)

    def edit_angle_and_color(
        self,
        image_bytes: bytes,
        horizontal: int,
        vertical: int,
        zoom: float,
        color_prompt: str,
    ) -> bytes:
        """앵글 먼저 적용 후 색감 적용 (순차).

        Args:
            image_bytes: Original image PNG bytes.
            horizontal: Horizontal rotation degrees.
            vertical: Vertical angle degrees.
            zoom: Zoom factor.
            color_prompt: Color/lighting change description.

        Returns:
            Edited image bytes (PNG) with both angle and color applied.
        """
        angle_result = self.edit_angle(image_bytes, horizontal, vertical, zoom)
        return self.edit_color(angle_result, color_prompt)
