"""이미지 품질 검증 모듈 — GPT-5.4 비전으로 생성된 이미지 품질 판정."""

import base64
import json
import socket
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any, Dict, List

from app.core.config import settings

PROMPTS_DIR = (
    Path(__file__).resolve().parent.parent.parent.parent
    / "prompts"
    / "_base"
    / "image_validation"
    / "v1"
)

OPENAI_API_URL = "https://api.openai.com/v1/responses"

VALIDATION_SCHEMA: Dict[str, Any] = {
    "type": "object",
    "additionalProperties": False,
    "properties": {
        "description": {"type": "string"},
        "score": {"type": "integer"},
        "passed": {"type": "boolean"},
        "issues": {
            "type": "array",
            "items": {"type": "string"},
        },
    },
    "required": ["description", "score", "passed", "issues"],
}

PASS_THRESHOLD = 60


def _load_prompt(filename: str) -> str:
    """Load a validation prompt template from disk."""
    return (PROMPTS_DIR / filename).read_text(encoding="utf-8").strip()


def _call_openai_vision(
    api_key: str,
    model: str,
    image_bytes: bytes,
    text_prompt: str,
) -> Dict[str, Any]:
    """Call OpenAI Responses API with image input for vision analysis."""
    b64_data = base64.b64encode(image_bytes).decode("ascii")

    body: Dict[str, Any] = {
        "model": model,
        "input": [
            {
                "type": "message",
                "role": "user",
                "content": [
                    {"type": "input_text", "text": text_prompt},
                    {
                        "type": "input_image",
                        "image_url": f"data:image/png;base64,{b64_data}",
                    },
                ],
            },
        ],
        "text": {
            "format": {
                "type": "json_schema",
                "name": "image_validation",
                "strict": True,
                "schema": VALIDATION_SCHEMA,
            }
        },
        "temperature": 0.1,
        "store": False,
    }

    req = urllib.request.Request(
        OPENAI_API_URL,
        data=json.dumps(body).encode("utf-8"),
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        method="POST",
    )

    timeout = settings.llm_timeout_validation
    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 resp:
                payload = json.loads(resp.read().decode("utf-8"))
            break
        except urllib.error.HTTPError as exc:
            error_text = exc.read().decode("utf-8", errors="replace")
            last_error = RuntimeError(f"OpenAI Vision 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"OpenAI Vision API failed after {max_retries} retries: {exc}") from exc
    else:
        raise RuntimeError(f"OpenAI Vision API failed: {last_error}")

    # Extract text from Responses API payload
    output_text = payload.get("output_text")
    if isinstance(output_text, str) and output_text.strip():
        return json.loads(output_text)

    output = payload.get("output")
    if isinstance(output, list):
        for item in output:
            if not isinstance(item, dict):
                continue
            content = item.get("content")
            if not isinstance(content, list):
                continue
            for part in content:
                if isinstance(part, dict) and part.get("type") == "output_text":
                    text = part.get("text")
                    if isinstance(text, str) and text.strip():
                        return json.loads(text)

    raise RuntimeError("OpenAI Vision response did not include output_text.")


def _format_traits_block(entity_info: Dict[str, Any]) -> str:
    """Format stable traits into a readable block."""
    stable_traits = entity_info.get("stable_traits", "{}")
    if isinstance(stable_traits, str):
        try:
            traits_data = json.loads(stable_traits)
        except json.JSONDecodeError:
            traits_data = {}
    else:
        traits_data = stable_traits

    visual_traits = traits_data.get("visual_anchor_traits", [])
    if not visual_traits and isinstance(traits_data, dict):
        for _k, v in traits_data.items():
            if isinstance(v, str):
                visual_traits.append(v)

    if not visual_traits:
        return "- (no specific traits listed)"
    return "\n".join(f"- {trait}" for trait in visual_traits[:8])


class ImageValidator:
    """GPT-5.4 비전으로 생성된 이미지 품질 판정."""

    def __init__(
        self,
        api_key: str | None = None,
        model: str | None = None,
    ) -> None:
        self._api_key = api_key or settings.openai_api_key
        self._model = model or settings.openai_model

    def validate_reference_image(
        self,
        image_bytes: bytes,
        entity_info: Dict[str, Any],
    ) -> Dict[str, Any]:
        """참조 이미지가 엔티티 설명과 일치하는지 검증.

        Args:
            image_bytes: PNG image bytes to validate.
            entity_info: Dict with keys: name, entity_type, description, stable_traits.

        Returns:
            {"score": 0-100, "passed": bool, "issues": [...], "description": str}
        """
        if not self._api_key:
            raise RuntimeError("OpenAI API key is not configured for image validation.")

        template = _load_prompt("reference_validation.md")
        prompt = template.format(
            entity_name=entity_info.get("name", ""),
            entity_type=entity_info.get("entity_type", ""),
            description=entity_info.get("description", ""),
            traits_block=_format_traits_block(entity_info),
            world_context=entity_info.get("world_context", "Not provided"),
        )

        result = _call_openai_vision(
            api_key=self._api_key,
            model=self._model,
            image_bytes=image_bytes,
            text_prompt=prompt,
        )

        # Enforce threshold
        score = result.get("score", 0)
        result["passed"] = score >= PASS_THRESHOLD
        return result

    def validate_scene_image(
        self,
        image_bytes: bytes,
        scene_info: Dict[str, Any],
        entity_names: List[str],
    ) -> Dict[str, Any]:
        """씬 이미지가 프롬프트 및 엔티티와 일치하는지 검증.

        Args:
            image_bytes: PNG image bytes to validate.
            scene_info: Dict with keys: scene_heading, beat_title, still_frame_prompt.
            entity_names: List of entity names expected to be visible.

        Returns:
            {"score": 0-100, "passed": bool, "issues": [...], "description": str}
        """
        if not self._api_key:
            raise RuntimeError("OpenAI API key is not configured for image validation.")

        template = _load_prompt("scene_validation.md")
        prompt = template.format(
            scene_heading=scene_info.get("scene_heading", ""),
            beat_title=scene_info.get("beat_title", ""),
            scene_prompt=scene_info.get("still_frame_prompt", ""),
            entity_names=", ".join(entity_names) if entity_names else "(none)",
            world_context=scene_info.get("world_context", "Not provided"),
        )

        result = _call_openai_vision(
            api_key=self._api_key,
            model=self._model,
            image_bytes=image_bytes,
            text_prompt=prompt,
        )

        score = result.get("score", 0)
        result["passed"] = score >= PASS_THRESHOLD
        return result

    def validate_pdf_page(
        self,
        pdf_page_image: bytes,
        expected_content: Dict[str, Any],
    ) -> Dict[str, Any]:
        """PDF 페이지가 올바르게 렌더링되었는지 검증.

        Args:
            pdf_page_image: PNG image bytes of the rendered PDF page.
            expected_content: Dict with keys: page_number, has_image, has_text, section_title.

        Returns:
            {"score": 0-100, "passed": bool, "issues": [...], "description": str}
        """
        if not self._api_key:
            raise RuntimeError("OpenAI API key is not configured for image validation.")

        template = _load_prompt("pdf_validation.md")
        prompt = template.format(
            page_number=expected_content.get("page_number", "unknown"),
            has_image=str(expected_content.get("has_image", False)),
            has_text=str(expected_content.get("has_text", True)),
            section_title=expected_content.get("section_title", ""),
        )

        result = _call_openai_vision(
            api_key=self._api_key,
            model=self._model,
            image_bytes=pdf_page_image,
            text_prompt=prompt,
        )

        score = result.get("score", 0)
        result["passed"] = score >= PASS_THRESHOLD
        return result
