"""LLM 호출 래퍼 (LangChain ChatOpenAI, Novita OpenAI 호환)."""
from __future__ import annotations

import base64
import json
import re
from functools import lru_cache
from typing import Any

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI

from app.config import settings


@lru_cache(maxsize=1)
def get_llm() -> ChatOpenAI:
    return ChatOpenAI(
        base_url=settings.llm_base_url,
        api_key=settings.llm_api_key,
        model=settings.llm_model,
        temperature=settings.llm_temperature,
        max_retries=2,
        timeout=120,
    )


def _strip_to_json(text: str) -> str:
    """코드펜스/잡텍스트 제거 후 첫 JSON object 추출."""
    t = text.strip()
    t = re.sub(r"^```(?:json)?", "", t).strip()
    t = re.sub(r"```$", "", t).strip()
    start = t.find("{")
    end = t.rfind("}")
    if start != -1 and end != -1 and end > start:
        return t[start : end + 1]
    return t


def parse_json(text: str) -> dict[str, Any]:
    return json.loads(_strip_to_json(text))


def call_vision(system_prompt: str, fewshot: str, image_bytes: bytes, mime: str = "image/png") -> str:
    """멀티모달 호출: 이미지 분석 → 텍스트(JSON 문자열) 반환."""
    b64 = base64.b64encode(image_bytes).decode("ascii")
    llm = get_llm()
    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(
            content=[
                {"type": "text", "text": fewshot + "\n\n위 예시를 참고하여 이 이미지를 IR JSON 으로 변환하라."},
                {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}},
            ]
        ),
    ]
    resp = llm.invoke(messages)
    return resp.content if isinstance(resp.content, str) else str(resp.content)


def call_vision_text(
    system_prompt: str,
    user_text: str,
    images: list[tuple[bytes, str]] | bytes,
    mime: str = "image/png",
) -> str:
    """멀티모달 호출: 1개 이상 이미지 + 구조화 텍스트 → 자유형 텍스트(HTML 등) 반환.

    images: (bytes, mime) 튜플 리스트(첨부 순서 보존). 하위호환으로 bytes 단일도 허용.
    """
    if isinstance(images, (bytes, bytearray)):
        images = [(bytes(images), mime)]
    parts: list[dict[str, Any]] = [{"type": "text", "text": user_text}]
    for img_bytes, img_mime in images:
        b64 = base64.b64encode(img_bytes).decode("ascii")
        parts.append({"type": "image_url", "image_url": {"url": f"data:{img_mime};base64,{b64}"}})
    llm = get_llm()
    messages = [SystemMessage(content=system_prompt), HumanMessage(content=parts)]
    resp = llm.invoke(messages)
    return resp.content if isinstance(resp.content, str) else str(resp.content)


def call_text(system_prompt: str, user_payload: dict[str, Any]) -> str:
    llm = get_llm()
    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(content=json.dumps(user_payload, ensure_ascii=False)),
    ]
    resp = llm.invoke(messages)
    return resp.content if isinstance(resp.content, str) else str(resp.content)
