"""
n8n Ultra Description API — Endpoint rieng cho n8n goi
POST /api/product-desc/n8n-generate

Flow:
  n8n gui: ten SP, chat lieu, tinh nang, link anh (URL)
  API tu fetch anh -> base64 -> Vision (Llama 4 Scout) -> Writer (GPT-OSS 120B)
  Tra ve: AI Description hoan chinh

n8n KHONG can gui base64 — chi can gui image_url (Link anh tu Sheets).
"""

import asyncio
import base64
import json
import logging
import re
import time

import httpx
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from openai import AsyncOpenAI
from pydantic import BaseModel

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/product-desc", tags=["n8n Ultra Description"])

from config import OPENAI_API_KEY

client = AsyncOpenAI(api_key=OPENAI_API_KEY)

# ── Config ──

VISION_MODELS = ["gpt-4.1-nano"]


# ── Helpers ──
async def _fetch_image_base64(url: str) -> str:
    """Fetch anh tu URL -> base64. n8n KHONG can gui base64."""
    async with httpx.AsyncClient(timeout=15, follow_redirects=True) as c:
        r = await c.get(url, headers={"User-Agent": "Mozilla/5.0"})
        r.raise_for_status()
        return base64.b64encode(r.content).decode()


def _extract_json(raw) -> dict | None:
    if isinstance(raw, dict):
        return raw
    if not isinstance(raw, str):
        raw = str(raw)
    if "```" in raw:
        m = re.search(r"```(?:json)?\s*([\s\S]*?)```", raw)
        if m:
            try:
                return json.loads(m.group(1).strip())
            except Exception:
                pass
    m = re.search(r"\{[\s\S]*\}", raw)
    if m:
        try:
            return json.loads(m.group(0))
        except Exception:
            pass
    try:
        return json.loads(raw.strip())
    except Exception:
        return None


async def _openai_call(
    messages: list, model: str, max_tokens: int = 2000, temperature: float = 0.7, is_vision: bool = False
) -> str:
    """OpenAI API call."""
    for _ in range(2):  # 2 retries
        try:
            payload = {
                "model": model,
                "messages": messages,
                "max_completion_tokens": max_tokens,
            }
            if not model.startswith("gpt-5"):
                payload["temperature"] = temperature

            resp = await client.chat.completions.create(**payload)
            return resp.choices[0].message.content
        except Exception as e:
            logger.warning(f"[n8n-desc] OpenAI error ({model}): {e}", exc_info=True)
            await asyncio.sleep(2)

    raise RuntimeError("All OpenAI retries exhausted")


async def _gemini_vision_call(prompt: str, img_b64: str, model_name: str) -> str:
    """Native Gemini API call cho Vision phase (vì proxy chặn mảng image)."""
    if not GOOGLE_API_KEY:
        raise ValueError("GOOGLE_API_KEY is missing in config.py")

    url = f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={GOOGLE_API_KEY}"
    payload = {
        "contents": [{"parts": [{"text": prompt}, {"inline_data": {"mime_type": "image/jpeg", "data": img_b64}}]}],
        "generationConfig": {"maxOutputTokens": 4000, "temperature": 0.7},
    }

    for _ in range(3):
        try:
            async with httpx.AsyncClient(timeout=60) as client:
                resp = await client.post(url, json=payload)
                if resp.status_code == 200:
                    return resp.json()["candidates"][0]["content"]["parts"][0]["text"]
                if resp.status_code == 429:
                    await asyncio.sleep(2)
                    continue
                raise Exception(f"Gemini API Error {resp.status_code}: {resp.text}")
        except Exception as e:
            logger.warning(f"Gemini Vision call failed: {e}")
            await asyncio.sleep(1)

    raise RuntimeError("Gemini Vision failed after retries")


# ═══════════════════════════════════════════════════════════════
# Request Schema — match voi data n8n gui tu Google Sheets
# ═══════════════════════════════════════════════════════════════
class N8nDescRequest(BaseModel):
    """
    n8n chi can gui cac field nay (lay thang tu Google Sheets).
    Chi co ten_san_pham va image_url la bat buoc.
    """

    # Bat buoc
    ten_san_pham: str
    image_url: str  # Link anh tu Sheets — API tu fetch

    # Tu Sheets (optional)
    ma_san_pham: str | None = None
    thanh_phan: str | None = None  # "100% polyamide"
    dac_tinh_chat_lieu: str | None = None  # "Nylon (Polyamide) nhanh kho..."
    tinh_nang: str | None = None
    kieu_dang: str | None = None  # Form
    mo_ta_chung: str | None = None
    hoan_canh: str | None = None
    featuring: str | None = None
    chung_chi: str | None = None
    huong_dan_su_dung: str | None = None


CANIFA_CATEGORIES = "Blazer, Bộ mặc nhà, Bộ quần áo, Bộ thể thao, Cardigan, Chân váy, Chăn cá nhân, Găng tay chống nắng, Khăn, Khăn mặt, Khăn tắm, Khẩu trang, Mũ, Mũ thể thao, Pyjama, Quần Body, Quần Khaki, Quần culottes, Quần dài, Quần giữ nhiệt, Quần jean, Quần leggings, Quần leggings mặc nhà, Quần lót, Quần lót tam giác, Quần lót đùi, Quần mặc nhà, Quần nỉ, Quần soóc, Quần váy, Túi xách, Tất, Váy liền, Áo Body, Áo Polo, Áo Sơ mi, Áo ba lỗ, Áo giữ nhiệt, Áo hai dây, Áo khoác, Áo khoác chần bông, Áo khoác chống nắng, Áo khoác dáng ngắn, Áo khoác dạ, Áo khoác gilet chần bông, Áo khoác gió, Áo khoác lông vũ, Áo khoác nỉ có mũ, Áo khoác nỉ không mũ, Áo khoác sợi, Áo kiểu, Áo len, Áo len gilet, Áo lót, Áo mặc nhà, Áo nỉ, Áo nỉ có mũ, Áo phông"


# ═══════════════════════════════════════════════════════════════
# Build Combined Prompt (Vision + Writer in 1 shot)
# ═══════════════════════════════════════════════════════════════
def _build_combined_prompt(req: N8nDescRequest) -> str:
    context_parts = [
        f"Tên SP: {req.ten_san_pham}",
        f"Mã SP: {req.ma_san_pham or 'Không rõ'}",
    ]
    if req.dac_tinh_chat_lieu:
        context_parts.append(f"Đặc tính chất liệu: {req.dac_tinh_chat_lieu}")
    if req.thanh_phan:
        context_parts.append(f"Thành phần: {req.thanh_phan}")
    if req.kieu_dang:
        context_parts.append(f"Form dáng: {req.kieu_dang}")
    if req.tinh_nang:
        context_parts.append(f"Tính năng: {req.tinh_nang}")
    if req.hoan_canh:
        context_parts.append(f"Dịp mặc: {req.hoan_canh}")
    if req.featuring:
        context_parts.append(f"Featuring: {req.featuring}")
    if req.mo_ta_chung:
        context_parts.append(f"Mô tả gốc: {req.mo_ta_chung}")

    context_block = "\n".join(f"- {p}" for p in context_parts)

    return f"""Bạn là AI Stylist cao cấp và Chuyên gia phân tích sản phẩm của Canifa.
Nhiệm vụ của bạn là NHÌN ẢNH SẢN PHẨM và KẾT HỢP VỚI THÔNG TIN BÊN DƯỚI để viết một bài đánh giá và mô tả sản phẩm thật CHI TIẾT, CHUYÊN SÂU và PHONG PHÚ nhất có thể (Ultra Deep Description).
Hãy viết thành văn bản hoàn chỉnh, không trả về định dạng JSON.

THÔNG TIN SẢN PHẨM (Từ hệ thống):
{context_block}

DANH SÁCH DANH MỤC CANIFA HỢP LỆ:
{CANIFA_CATEGORIES}

QUY TẮC VIẾT:
1. Giọng văn: Chuyên nghiệp, phân tích chuyên sâu như một stylist và kỹ sư dệt may, nhưng vẫn truyền cảm hứng.
2. Độ dài: CỰC KỲ DÀI (khoảng 1000 - 2000 từ). Chia thành nhiều đoạn văn rõ ràng để tạo thành bài viết siêu chi tiết.
3. BẮT BUỘC phải đào sâu vào các khía cạnh sau:
   - Nhận diện Form & Kiểu dáng từ ảnh: Sản phẩm là dáng áo/quần/váy gì. Sử dụng ĐÚNG tên từ DANH SÁCH DANH MỤC CANIFA. Cổ áo/tay/gấu được xử lý như thế nào? Chi tiết cắt may giúp tôn dáng?
   - Phân tích Chất liệu cực sâu: Dựa vào thành phần cho sẵn và bề mặt vải trong ảnh. Ứng dụng thực tế trong điều kiện thời tiết.
   - Hoàn cảnh sử dụng (Lifestyle): Vẽ ra 3-4 kịch bản chi tiết dựa vào cảm giác từ ảnh.
   - Gợi ý Mix & Match (Outfit): Đề xuất 3-4 outfits chi tiết từ đầu đến chân phù hợp với sản phẩm trong ảnh. Ưu tiên đồ Canifa.
   - Vibe & Thần thái: Toát lên phong cách gì?
4. KHÔNG bịa thành phần chất liệu (chỉ dùng thông tin đã cho). KHÔNG đề cập giá cả.
5. XÁC ĐỊNH ĐÚNG LOẠI SẢN PHẨM vào 1 danh mục Canifa.
6. Sử dụng tiếng Việt tự nhiên, từ vựng phong phú, đặc tả tốt. Trả về bài viết trực tiếp.
"""


# ═══════════════════════════════════════════════════════════════
# Endpoint
# ═══════════════════════════════════════════════════════════════
@router.post("/n8n-generate", summary="n8n Ultra Description — Sinh AI Description tu anh + metadata")
async def n8n_generate_description(req: N8nDescRequest):
    """
    Endpoint rieng cho n8n workflow:
      1. Nhan metadata tu Google Sheets (ten, chat lieu, link anh...)
      2. Fetch anh tu URL -> base64
      3. Vision + Writer trong 1 call de toi uu thoi gian va token.
      4. Tra ve AI Description hoan chinh.
    """
    t0 = time.time()
    ma_sp = req.ma_san_pham or "UNKNOWN"

    try:
        logger.info(f"[n8n-desc] {ma_sp} — ONE PHASE: Vision+Writer ({VISION_MODELS})")

        img_b64 = await _fetch_image_base64(req.image_url)
        logger.info(f"[n8n-desc] {ma_sp} — Image fetched: {len(img_b64)} chars base64")

        combined_prompt = _build_combined_prompt(req)
        ai_desc = None

        for v_model in VISION_MODELS:
            try:
                logger.info(f"[n8n-desc] {ma_sp} — Trying Model: {v_model}")
                if v_model.startswith("gemini"):
                    ai_desc = await _gemini_vision_call(combined_prompt, img_b64, v_model)
                else:
                    vision_messages = [
                        {
                            "role": "user",
                            "content": [
                                {
                                    "type": "image_url",
                                    "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"},
                                },
                                {"type": "text", "text": combined_prompt},
                            ],
                        }
                    ]
                    ai_desc = await _openai_call(vision_messages, v_model, max_tokens=4000, is_vision=True)
                if ai_desc:
                    break
            except Exception as e:
                logger.warning(f"[n8n-desc] {ma_sp} — Failed with {v_model}: {e}")
                continue

        if not ai_desc:
            return JSONResponse(
                status_code=500,
                content={
                    "status": "error",
                    "ma_san_pham": ma_sp,
                    "message": "AI khong tra ve ket qua hop le tu buc anh.",
                },
            )

        ai_desc = ai_desc.strip()
        elapsed = round(time.time() - t0, 2)
        logger.info(f"[n8n-desc] {ma_sp} — Done in {elapsed}s")

        return {
            "status": "success",
            "ma_san_pham": ma_sp,
            "ten_san_pham": req.ten_san_pham,
            "ai_description": ai_desc,
            "elapsed_s": elapsed,
        }

    except Exception as e:
        logger.exception(f"[n8n-desc] {ma_sp} — Unhandled error: {e}")
        return JSONResponse(
            status_code=500,
            content={
                "status": "error",
                "ma_san_pham": ma_sp,
                "message": str(e),
            },
        )
