"""
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
from typing import Optional

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

logger = logging.getLogger(__name__)

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

from config import FREE_LLM_API_KEY, LITELLM_BASE_URL

# ── FreeLLMAPI config ──

VISION_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct"
WRITER_MODEL = "openai/gpt-oss-20b"


# ── 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 _groq_call(messages: list, model: str, max_tokens: int = 2000,
                     temperature: float = 0.7, is_vision: bool = False) -> str:
    """FreeLLMAPI call voi fallback chain."""
    fallbacks = [model]
    if not is_vision:
        if model != "openai/gpt-oss-120b":
            fallbacks.append("openai/gpt-oss-120b")
        if model != "openai/gpt-oss-20b":
            fallbacks.append("openai/gpt-oss-20b")

    for m in fallbacks:
        for _ in range(2):  # 2 retries
            try:
                async with httpx.AsyncClient(timeout=45) as c:
                    resp = await c.post(
                        f"{LITELLM_BASE_URL}/v1/chat/completions",
                        headers={"Authorization": f"Bearer {FREE_LLM_API_KEY}",
                                 "Content-Type": "application/json"},
                        json={"model": m, "max_tokens": max_tokens,
                              "temperature": temperature, "messages": messages},
                    )
                if resp.status_code == 200:
                    data = resp.json()
                    if "error" not in data:
                        return data["choices"][0]["message"]["content"]
                elif resp.status_code == 429:
                    await asyncio.sleep(1)
                    continue  # retry
                else:
                    break  # loi khac -> thu model tiep
            except Exception as e:
                logger.warning(f"FreeLLMAPI error ({m}): {e}")
                break
        await asyncio.sleep(2)

    raise RuntimeError("All FreeLLMAPI models/retries exhausted")


# ═══════════════════════════════════════════════════════════════
# 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: Optional[str] = None
    thanh_phan: Optional[str] = None         # "100% polyamide"
    dac_tinh_chat_lieu: Optional[str] = None  # "Nylon (Polyamide) nhanh kho..."
    tinh_nang: Optional[str] = None
    kieu_dang: Optional[str] = None          # Form
    mo_ta_chung: Optional[str] = None
    hoan_canh: Optional[str] = None
    featuring: Optional[str] = None
    chung_chi: Optional[str] = None
    huong_dan_su_dung: Optional[str] = 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"

# ═══════════════════════════════════════════════════════════════
# Phase 1 — Vision: Llama 4 Scout nhin anh
# ═══════════════════════════════════════════════════════════════
def _build_vision_prompt(req: N8nDescRequest) -> str:
    extra = []
    if req.dac_tinh_chat_lieu:
        extra.append(f"- Chất liệu (đã biết, KHÔNG đoán lại): {req.dac_tinh_chat_lieu}")
    if req.thanh_phan:
        extra.append(f"- Thành phần: {req.thanh_phan}")
    if req.kieu_dang:
        extra.append(f"- Form dáng: {req.kieu_dang}")
    if req.tinh_nang:
        extra.append(f"- Tính năng vải: {req.tinh_nang}")
    if req.hoan_canh:
        extra.append(f"- Dịp mặc mong muốn: {req.hoan_canh}")
    if req.featuring:
        extra.append(f"- Điểm nhấn thiết kế: {req.featuring}")

    extra_block = "\n".join(extra) if extra else "Không có thông tin bổ sung."

    return f"""Bạn là chuyên gia phân tích sản phẩm thời trang Canifa.
Nhìn ảnh sản phẩm và kết hợp thông tin đã biết để trả về JSON sau:

THÔNG TIN ĐÃ BIẾT:
- Tên SP: {req.ten_san_pham}
{extra_block}

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

QUAN TRỌNG:
- Nếu đã có chất liệu -> GIỮ NGUYÊN, không đoán lại
- Chỉ nhận xét những gì THẤY ĐƯỢC trong ảnh
- Phân loại sản phẩm này vào ĐÚNG 1 DANH MỤC trong danh sách trên.
- Trả về JSON (không text thừa):

{{
  "mau_sac": "Màu sắc chính nhìn thấy trong ảnh",
  "danh_muc": "Lấy 1 tên danh mục chuẩn từ danh sách trên",
  "form_dang": "Form dáng (Slim/Regular/Oversize/...)",
  "dac_trung_thiet_ke": "2-3 đặc trưng thiết kế nổi bật",
  "chat_lieu_nhan_dien": "Chất liệu (dùng thông tin đã biết nếu có)",
  "phong_cach": "Phong cách tổng thể (1-3 từ khóa)",
  "doi_tuong": "Đối tượng phù hợp",
  "dip_su_dung": "2-3 dịp sử dụng phù hợp"
}}"""


# ═══════════════════════════════════════════════════════════════
# Phase 2 — Writer: GPT-OSS 120B viet Ultra Description
# ═══════════════════════════════════════════════════════════════
def _build_writer_prompt(req: N8nDescRequest, vision_data: dict) -> str:
    # Uu tien: chat lieu tu Sheets > vision > fallback
    final_material = (
        req.dac_tinh_chat_lieu
        or req.thanh_phan
        or vision_data.get("chat_lieu_nhan_dien", "")
        or "Theo thông tin nhà sản xuất"
    )

    context_parts = [
        f"Tên SP: {req.ten_san_pham}",
        f"Chất liệu: {final_material}",
        f"Thành phần: {req.thanh_phan or 'Không rõ'}",
    ]
    if vision_data.get("danh_muc"):
        context_parts.append(f"Danh mục phân loại: {vision_data['danh_muc']}")
    if req.kieu_dang:
        context_parts.append(f"Form dáng: {req.kieu_dang}")
    elif vision_data.get("form_dang"):
        context_parts.append(f"Form dáng (từ ảnh): {vision_data['form_dang']}")
    if vision_data.get("mau_sac"):
        context_parts.append(f"Màu sắc: {vision_data['mau_sac']}")
    if vision_data.get("dac_trung_thiet_ke"):
        context_parts.append(f"Đặc trưng thiết kế: {vision_data['dac_trung_thiet_ke']}")
    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}")
    elif vision_data.get("dip_su_dung"):
        context_parts.append(f"Dịp mặc (từ ảnh): {vision_data['dip_su_dung']}")
    if vision_data.get("phong_cach"):
        context_parts.append(f"Phong cách: {vision_data['phong_cach']}")
    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à 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).
Bài viết này sẽ được dùng làm "raw data" cho các hệ thống khác tổng hợp lại, nên hãy viết càng dài, càng nhiều thông tin, càng nhiều context càng tốt.

THÔNG TIN SẢN PHẨM:
{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ừ). Không giới hạn sự sáng tạo, viết thành rất nhiều đoạn văn hoặc các phần rõ ràng để tạo thành một bài viết cực kỳ dài, chi tiết đến từng centimet.
3. BẮT BUỘC phải đào sâu vào các khía cạnh sau với độ chi tiết cực độ:
   - Nhận diện Form & Kiểu dáng: 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 phía trên. Cổ áo/tay áo/gấu quần được xử lý như thế nào? Chi tiết cắt may nào giúp tôn dáng hoặc che khuyết điểm?
   - Phân tích Chất liệu cực sâu: Bề mặt vải nhìn dưới ánh sáng sẽ thế nào (lì, bóng nhẹ, thô, mềm...)? Khi sờ vào hay mặc lên da cảm giác ra sao? Ứng dụng thực tế của chất liệu đó trong điều kiện thời tiết khác nhau (thấm hút mồ hôi, giữ ấm, chống UV...).
   - Hoàn cảnh sử dụng (Lifestyle): Vẽ ra ít nhất 3-4 kịch bản chi tiết. (Ví dụ: "Khi bạn ở phòng gym vào buổi sáng...", "Một chiều dạo phố cuối tuần...").
   - Gợi ý Mix & Match (Outfit): Đề xuất 3-4 outfits cực kỳ chi tiết từ đầu đến chân (áo, quần/váy, giày, phụ kiện, túi xách). ƯU TIÊN gợi ý các sản phẩm thuộc DANH SÁCH DANH MỤC CANIFA. Giải thích nguyên lý thị giác TẠI SAO outfit đó lại ăn nhập với nhau.
   - Vibe & Thần thái: Toát lên phong cách gì? Định vị cá nhân của người mặc là ai? (Năng động, quý phái, bụi bặm...).
4. KHÔNG bịa chất liệu. KHÔNG đề cập giá cả.
5. XÁC ĐỊNH ĐÚNG LOẠI SẢN PHẨM: TUYỆT ĐỐI phải xác định sản phẩm thuộc 1 trong các danh mục Canifa. TUYỆT ĐỐI KHÔNG gọi sai tên loại sản phẩm.
6. Sử dụng tiếng Việt tự nhiên, từ vựng phong phú, đặc tả tốt.

VÍ DỤ MINH HỌA CÁCH VIẾT (Để hiểu độ sâu và chi tiết cần có):
- Hoàn cảnh sử dụng: "Trong những buổi sáng cuối tuần thảnh thơi dạo bước trên phố, chiếc Áo ba lỗ này là lựa chọn hoàn hảo để giải phóng cơ thể khỏi sự gò bó của trang phục công sở. Hoặc trong các buổi tập gym cường độ cao, chất liệu thấm hút nhanh sẽ hỗ trợ tối đa..."
- Mix & Match: "Outfit 1 (Năng động ngày hè): Kết hợp chiếc Áo ba lỗ này cùng một chiếc Quần soóc khaki. Form đứng của Quần soóc sẽ bù trừ hoàn hảo cho độ mềm mại của áo, tạo nên tỷ lệ cơ thể vững chãi nam tính. Thêm một đôi sneaker trắng và Mũ thể thao Canifa để hoàn thiện diện mạo..."

Trả về JSON:
{{
  "ai_description": "Bài viết mô tả cực kỳ chi tiết và chuyên sâu (1000-2000 từ)"
}}"""


# ═══════════════════════════════════════════════════════════════
# 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 (n8n KHONG can gui base64)
      3. Vision (Llama 4 Scout) -> nhin anh, extract thong tin
      4. Writer (GPT-OSS 120B) -> viet Ultra Description
      5. Tra ve AI Description hoan chinh

    n8n HTTP Request node chi can POST JSON voi cac field tu Sheets.
    """
    t0 = time.time()
    ma_sp = req.ma_san_pham or "UNKNOWN"

    try:
        # ── Phase 1: Vision ──
        logger.info(f"[n8n-desc] {ma_sp} — Phase 1: Vision ({VISION_MODEL})")
        vision_data = {}

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

            vision_messages = [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "image_url",
                            "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"},
                        },
                        {"type": "text", "text": _build_vision_prompt(req)},
                    ],
                }
            ]
            vision_raw = await _groq_call(
                vision_messages, VISION_MODEL,
                max_tokens=800, is_vision=True
            )
            vision_data = _extract_json(vision_raw) or {}
            logger.info(f"[n8n-desc] {ma_sp} — Vision done: {list(vision_data.keys())}")

        except Exception as vision_err:
            # Vision loi -> van chay tiep voi data rong
            logger.warning(f"[n8n-desc] {ma_sp} — Vision failed: {vision_err}")

        # ── Phase 2: Writer ──
        logger.info(f"[n8n-desc] {ma_sp} — Phase 2: Writer ({WRITER_MODEL})")

        writer_messages = [
            {
                "role": "system",
                "content": (
                    "Bạn là AI Stylist cao cấp Canifa. Viết mô tả sản phẩm thời trang "
                    "bằng tiếng Việt. Phong cách: tự tin, gợi hình, không bịa chất liệu. "
                    "Luôn trả về JSON chuẩn, không text thừa."
                ),
            },
            {"role": "user", "content": _build_writer_prompt(req, vision_data)},
        ]

        writer_raw = await _groq_call(
            writer_messages, WRITER_MODEL,
            max_tokens=4000
        )
        result = _extract_json(writer_raw)

        if not result or not result.get("ai_description"):
            # Fallback: neu JSON fail, lay raw text lam description
            ai_desc = writer_raw.strip() if writer_raw else ""
            if not ai_desc:
                return JSONResponse(
                    status_code=500,
                    content={
                        "status": "error",
                        "ma_san_pham": ma_sp,
                        "message": "AI writer khong tra ve ket qua hop le.",
                        "raw": writer_raw[:500] if writer_raw else None,
                    },
                )
            result = {"ai_description": ai_desc}

        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": result["ai_description"],
            "elapsed_s": elapsed,
            # Debug (n8n co the bo qua)
            "vision_data": vision_data,
        }

    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),
            },
        )
