"""
Ultra Descriptions Agent — Main LangGraph pipeline.

Flow:
  1. fetch_product_data  → StarRocks ground truth
  2. vision_analysis     → Image → design/motif details (KHÔNG đặt lại tên)
  3. write_description   → Grounded 28-field JSON generation
  4. review_description  → Self-critique: fix tên, giới tính, chất liệu
  5. save_to_db          → Upsert vào Postgres via UltraDescriptionDB

Usage:
    result = await run_ultra_description_agent(
        internal_ref_code="5TS25S021",
        magento_ref_code="5TS25S021-SR079",
    )
"""
from __future__ import annotations

import asyncio
import base64
import json
import logging
import re
import time
from typing import TypedDict, Optional, Any

import httpx
from langgraph.graph import StateGraph, END

from common.starrocks_connection import get_db_connection
from common.ultra_desc_db import UltraDescriptionDB
from .prompts import (
    VISION_PROMPT_TEMPLATE, WRITER_SYSTEM, WRITER_PROMPT_TEMPLATE,
    REVIEWER_PROMPT_TEMPLATE, GENDER_MAP, AGE_MAP,
)

logger = logging.getLogger(__name__)

TABLE_NAME = "shared_source.magento_product_dimension_with_text_embedding"

# ── Groq config (reuse từ product_desc_route) ──
GROQ_API_KEYS = [
    "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",
    "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",
    "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",
    "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",
    "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",
    "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",
]
_key_idx = 0

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


# ══════════════════════════════════════════════════════════════
# State
# ══════════════════════════════════════════════════════════════
class AgentState(TypedDict):
    # Input
    internal_ref_code: str
    magento_ref_code: Optional[str]

    # DB Ground Truth (set in step 1 — immutable after that)
    product_name: str
    product_line: str
    gender_raw: str
    gender_label: str
    age_raw: str
    age_label: str
    colors: str
    color_count: int
    size_scale: str                 # Formatted string VD: "S, M, L, XL"
    size_scale_arr: list            # Raw array from DB VD: ["S","M","L","XL"]
    sizeguide_content: str          # HTML/text từ magento_product_size_scale_with_sizeguide
    quantity_sold: int
    description_text: str           # Magento description (source of material info)
    image_url: str
    image_base64: Optional[str]

    # Step outputs
    vision_result: Optional[dict]   # Parsed vision JSON
    description_data: Optional[dict]  # 28-field JSON (with luu_y_size from size guide)
    reviewed_data: Optional[dict]     # After self-review

    # Error tracking
    error: Optional[str]
    step: str


# ══════════════════════════════════════════════════════════════
# Helpers
# ══════════════════════════════════════════════════════════════
def _next_key() -> str:
    global _key_idx
    key = GROQ_API_KEYS[_key_idx % len(GROQ_API_KEYS)]
    _key_idx += 1
    return key


def _extract_json(raw: str) -> dict | None:
    if isinstance(raw, dict):
        return raw
    if not isinstance(raw, str):
        raw = str(raw)
    # ```json ... ```
    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
    return None


async def _groq_call(
    messages: list,
    model: str = WRITER_MODEL,
    max_tokens: int = 4000,
    temperature: float = 0.65,
    is_vision: bool = False,
) -> str:
    """Multi-key round-robin Groq call with retry."""
    fallback_chain = [model]
    if not is_vision:
        for fb in [WRITER_MODEL, REVIEWER_MODEL, "qwen/qwen3-32b"]:
            if fb != model and fb not in fallback_chain:
                fallback_chain.append(fb)

    for groq_model in fallback_chain:
        for _ in range(len(GROQ_API_KEYS)):
            api_key = _next_key()
            try:
                async with httpx.AsyncClient(timeout=45) as client:
                    resp = await client.post(
                        "https://api.groq.com/openai/v1/chat/completions",
                        headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
                        json={"model": groq_model, "max_tokens": max_tokens, "temperature": temperature, "messages": messages},
                    )
                    if resp.status_code == 200:
                        data = resp.json()
                        if "error" not in data:
                            logger.info("✅ Groq [%s] OK", groq_model)
                            return data["choices"][0]["message"]["content"]
                    elif resp.status_code == 429:
                        logger.warning("⚠️ Groq rate limit %s → rotate key", groq_model)
                        continue
                    else:
                        logger.warning("⚠️ Groq %s HTTP %s", groq_model, resp.status_code)
                        break
            except Exception as e:
                logger.warning("⚠️ Groq %s exception: %s", groq_model, e)
                break
        await asyncio.sleep(2)

    raise RuntimeError("All Groq models/keys exhausted")


async def _fetch_image_b64(url: str) -> str:
    async with httpx.AsyncClient(timeout=15) as client:
        r = await client.get(url, headers={"User-Agent": "Mozilla/5.0"})
        r.raise_for_status()
        return base64.b64encode(r.content).decode()


def _extract_material_from_desc(description_text: str) -> str:
    """Extract chất liệu từ Magento description text."""
    if not description_text:
        return ""
    text = description_text.lower()
    # Common patterns
    patterns = [
        r'(?:chất liệu|chất lieau|material|vải)[:\s]+([^\n\.;]{5,80})',
        r'(\d+%\s*(?:cotton|polyester|spandex|modal|tencel|linen|bamboo|viscose|nylon)[^\n\.;]{0,60})',
        r'(?:cotton|polyester|spandex|modal|tencel)[^\n\.;]{0,60}',
    ]
    for pat in patterns:
        m = re.search(pat, description_text, re.IGNORECASE)
        if m:
            material = m.group(1) if m.lastindex else m.group(0)
            return material.strip()[:100]
    return ""


def _name_to_tags(product_name: str) -> str:
    """Convert product name to comma-separated tags."""
    words = product_name.lower().replace("người lớn", "").replace("  ", " ").strip().split()
    return ", ".join(w for w in words if len(w) > 2)[:100]


def _vision_summary(vision: dict | None) -> str:
    if not vision:
        return "sản phẩm thời trang"
    parts = []
    if vision.get("hinh_thuc"):
        parts.append(vision["hinh_thuc"])
    if vision.get("form_dang"):
        parts.append(vision["form_dang"])
    return "; ".join(parts[:2]) or "sản phẩm thời trang"


# ══════════════════════════════════════════════════════════════
# Node 1: Fetch product data from StarRocks
# ══════════════════════════════════════════════════════════════
async def node_fetch_product(state: AgentState) -> AgentState:
    logger.info("[ULTRA-AGENT] 📦 Fetching product: %s / %s", state["internal_ref_code"], state.get("magento_ref_code"))
    db = get_db_connection()

    base_code = state["internal_ref_code"]
    magento_code = state.get("magento_ref_code")

    # Try internal_ref_code first
    rows = await db.execute_query_async(
        f"""
        SELECT
            internal_ref_code,
            ANY_VALUE(product_name) AS product_name,
            ANY_VALUE(product_image_url_thumbnail) AS image_url,
            ANY_VALUE(product_line_vn) AS product_line,
            ANY_VALUE(gender_by_product) AS gender,
            ANY_VALUE(age_by_product) AS age,
            MAX(quantity_sold) AS quantity_sold,
            ANY_VALUE(size_scale) AS size_scale,
            COUNT(DISTINCT product_color_code) AS color_count,
            GROUP_CONCAT(DISTINCT master_color SEPARATOR ', ') AS colors,
            ANY_VALUE(description_text) AS description_text
        FROM {TABLE_NAME}
        WHERE internal_ref_code = %s
        GROUP BY internal_ref_code
        """,
        params=(base_code,),
    )

    if not rows and magento_code:
        rows = await db.execute_query_async(
            f"""
            SELECT
                internal_ref_code,
                ANY_VALUE(product_name) AS product_name,
                ANY_VALUE(product_image_url_thumbnail) AS image_url,
                ANY_VALUE(product_line_vn) AS product_line,
                ANY_VALUE(gender_by_product) AS gender,
                ANY_VALUE(age_by_product) AS age,
                MAX(quantity_sold) AS quantity_sold,
                ANY_VALUE(size_scale) AS size_scale,
                COUNT(DISTINCT product_color_code) AS color_count,
                GROUP_CONCAT(DISTINCT master_color SEPARATOR ', ') AS colors,
                ANY_VALUE(description_text) AS description_text
            FROM {TABLE_NAME}
            WHERE magento_ref_code = %s
            GROUP BY internal_ref_code
            """,
            params=(magento_code,),
        )

    if not rows:
        return {**state, "error": f"Không tìm thấy sản phẩm: {base_code}", "step": "fetch_failed"}

    p = rows[0]
    gender_raw = (p.get("gender") or "unisex").lower()
    age_raw = (p.get("age") or "adult").lower()

    # ── Fetch size guide from magento_product_size_scale_with_sizeguide ──
    sizeguide_content = ""
    size_scale_arr = []
    try:
        sg_rows = await db.execute_query_async(
            """
            SELECT size_scale, sizeguide_content
            FROM test_db.magento_product_size_scale_with_sizeguide
            WHERE internal_ref_code = %s
            LIMIT 1
            """,
            params=(p.get("internal_ref_code") or base_code,),
        )
        if sg_rows:
            raw_arr = sg_rows[0].get("size_scale") or []
            if isinstance(raw_arr, str):
                try:
                    raw_arr = json.loads(raw_arr)
                except Exception:
                    raw_arr = [s.strip() for s in raw_arr.split(",") if s.strip()]
            size_scale_arr = raw_arr
            sizeguide_content = sg_rows[0].get("sizeguide_content") or ""
            logger.info("[ULTRA-AGENT] 📐 Size guide fetched: %s sizes, guide_len=%s",
                        len(size_scale_arr), len(sizeguide_content))
    except Exception as sg_err:
        logger.warning("[ULTRA-AGENT] ⚠️ Size guide fetch failed: %s", sg_err)

    # Format size_scale string for display
    size_scale_str = ", ".join(size_scale_arr) if size_scale_arr else (p.get("size_scale") or "S, M, L, XL")

    return {
        **state,
        "product_name": p.get("product_name") or base_code,
        "product_line": p.get("product_line") or "",
        "gender_raw": gender_raw,
        "gender_label": GENDER_MAP.get(gender_raw, gender_raw.title()),
        "age_raw": age_raw,
        "age_label": AGE_MAP.get(age_raw, age_raw.title()),
        "colors": p.get("colors") or "",
        "color_count": int(p.get("color_count") or 1),
        "size_scale": size_scale_str,
        "size_scale_arr": size_scale_arr,
        "sizeguide_content": sizeguide_content,
        "quantity_sold": int(p.get("quantity_sold") or 0),
        "description_text": p.get("description_text") or "",
        "image_url": p.get("image_url") or "",
        "image_base64": None,
        "vision_result": None,
        "description_data": None,
        "reviewed_data": None,
        "error": None,
        "step": "fetched",
    }


# ══════════════════════════════════════════════════════════════
# Node 2: Vision analysis (grounded by product_name)
# ══════════════════════════════════════════════════════════════
async def node_vision(state: AgentState) -> AgentState:
    if state.get("error"):
        return state

    image_url = state["image_url"]
    if not image_url:
        logger.warning("[ULTRA-AGENT] ⚠️ No image URL — skipping vision")
        return {**state, "vision_result": {}, "step": "vision_skipped"}

    logger.info("[ULTRA-AGENT] 📸 Vision analysis: %s", state["product_name"])

    try:
        image_b64 = await _fetch_image_b64(image_url)
    except Exception as e:
        logger.warning("[ULTRA-AGENT] ⚠️ Image fetch failed: %s — skipping vision", e)
        return {**state, "image_base64": None, "vision_result": {}, "step": "vision_skipped"}

    prompt = VISION_PROMPT_TEMPLATE.format(
        product_name=state["product_name"],
        product_line=state["product_line"],
        gender_label=state["gender_label"],
        age_label=state["age_label"],
        colors=state["colors"],
    )

    messages = [
        {"role": "system", "content": "Bạn là chuyên gia phân tích thiết kế thời trang. Trả về JSON thuần tuý."},
        {
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}},
                {"type": "text", "text": prompt},
            ],
        },
    ]

    raw = await _groq_call(messages, model=VISION_MODEL, max_tokens=1000, temperature=0.1, is_vision=True)
    vision = _extract_json(raw) or {}
    logger.info("[ULTRA-AGENT] ✅ Vision done: %s", str(vision)[:120])

    return {**state, "image_base64": image_b64, "vision_result": vision, "step": "vision_done"}


# ══════════════════════════════════════════════════════════════
# Node 3: Writer — generate grounded 28-field description
# ══════════════════════════════════════════════════════════════
async def node_write(state: AgentState) -> AgentState:
    if state.get("error"):
        return state

    logger.info("[ULTRA-AGENT] ✍️ Writing description: %s", state["product_name"])

    vision = state.get("vision_result") or {}
    vision_str = json.dumps(vision, ensure_ascii=False, indent=2) if vision else "Không có ảnh"
    mat_from_db = _extract_material_from_desc(state["description_text"])

    prompt = WRITER_PROMPT_TEMPLATE.format(
        product_name=state["product_name"],
        product_line=state["product_line"],
        gender_raw=state["gender_raw"],
        gender_label=state["gender_label"],
        age_raw=state["age_raw"],
        age_label=state["age_label"],
        colors=state["colors"],
        color_count=state["color_count"],
        size_scale=state["size_scale"],
        quantity_sold=state["quantity_sold"],
        description_text=state["description_text"][:800] if state["description_text"] else "Không có",
        vision_analysis=vision_str,
        vision_summary=_vision_summary(vision),
        name_tags=_name_to_tags(state["product_name"]),
    )

    messages = [
        {"role": "system", "content": WRITER_SYSTEM},
        {"role": "user", "content": prompt},
    ]

    raw = await _groq_call(messages, model=WRITER_MODEL, max_tokens=4000, temperature=0.65)
    desc_data = _extract_json(raw)

    if not desc_data:
        return {**state, "error": "Writer failed to produce valid JSON", "step": "write_failed"}

    # Force-inject ground truth fields (guardrail)
    desc_data["ten_san_pham"] = state["product_name"]
    desc_data["gioi_tinh"] = state["gender_label"]
    desc_data["do_tuoi"] = state["age_label"]
    # chat_lieu: DB ground truth WINS unconditionally if found
    if mat_from_db:
        desc_data["chat_lieu"] = mat_from_db

    logger.info("[ULTRA-AGENT] ✅ Write done: ten_san_pham='%s'", desc_data.get("ten_san_pham"))
    return {**state, "description_data": desc_data, "step": "written"}


# ══════════════════════════════════════════════════════════════
# Node 3b: Size Guide — generate luu_y_size from real size data
# ══════════════════════════════════════════════════════════════
async def node_size_guide(state: AgentState) -> AgentState:
    """Generate luu_y_size field from real sizeguide_content."""
    if state.get("error"):
        return state

    desc_data = state.get("description_data") or {}
    sizeguide = state.get("sizeguide_content") or ""
    size_scale = state.get("size_scale") or ""

    if not sizeguide and not size_scale:
        logger.info("[ULTRA-AGENT] 📐 No size data — keeping AI-generated luu_y_size")
        return state

    logger.info("[ULTRA-AGENT] 📐 Generating size guide from real data...")

    context_str = "\n".join(filter(None, [
        f"- Tên SP: {state['product_name']}",
        f"- Dòng SP: {state['product_line']}" if state.get('product_line') else "",
        f"- Giới tính: {state['gender_label']}",
        f"- Độ tuổi: {state['age_label']}",
        f"- Chất liệu: {desc_data.get('chat_lieu', 'Không có')}" ,
        f"- Phong cách: {desc_data.get('phong_cach', '')}" if desc_data.get('phong_cach') else "",
    ]))

    size_info = f"Bảng size có: {size_scale}" if size_scale else ""
    if sizeguide:
        size_info += f"\n\nNội dung bảng size chi tiết:\n{sizeguide[:1500]}"

    prompt = f"""Bạn là Stylist thời trang chuyên nghiệp của Canifa.
Dựa trên thông tin sản phẩm và bảng size thực tế, viết đoạn Hướng dẫn chọn size tự nhiên, dễ hiểu và chính xác.

THÔNG TIN SẢN PHẨM:
{context_str}

DỮ LIỆU SIZE THỰC TẾ:
{size_info}

YÊU CẦU:
- Liệt kê các size có sẵn ({size_scale}) dựa trên bảng thông số thực tế
- Đưa ra ví dụ cụ thể: "Người cao Xcm, nặng Ykg → nên chọn size Z"
- Tips mặc đẹp phù hợp với phong cách và giới tính của sản phẩm
- Trình bày bảng thông số size đẹp nếu có data (chiều cao, cân nặng, số đo)
- Ngắn gọn, thực dụng, không rườm rà
- CHỈ trả về nội dung hướng dẫn, KHÔNG giải thích thêm"""

    messages = [
        {"role": "system", "content": "Bạn là AI phụ trách nội dung sản phẩm thời trang Canifa."},
        {"role": "user", "content": prompt},
    ]

    try:
        raw = await _groq_call(messages, model=REVIEWER_MODEL, max_tokens=800, temperature=0.6)
        luu_y_size = raw.strip().strip('"').strip("'")
        if luu_y_size:
            desc_data["luu_y_size"] = luu_y_size
            logger.info("[ULTRA-AGENT] ✅ Size guide generated (%s chars)", len(luu_y_size))
    except Exception as e:
        logger.warning("[ULTRA-AGENT] ⚠️ Size guide gen failed: %s — keeping original", e)

    return {**state, "description_data": desc_data, "step": "size_guide_done"}


# ══════════════════════════════════════════════════════════════
# Node 4: Reviewer — self-critique & fix
# ══════════════════════════════════════════════════════════════
async def node_review(state: AgentState) -> AgentState:
    if state.get("error"):
        return state

    desc_data = state["description_data"]
    logger.info("[ULTRA-AGENT] 🔍 Reviewing description...")

    mat_from_db = _extract_material_from_desc(state["description_text"])

    prompt = REVIEWER_PROMPT_TEMPLATE.format(
        product_name=state["product_name"],
        gender_label=state["gender_label"],
        age_label=state["age_label"],
        chat_lieu_from_db=mat_from_db or "Không tìm thấy trong DB",
        description_json=json.dumps(desc_data, ensure_ascii=False, indent=2),
    )

    messages = [
        {"role": "system", "content": "Bạn là QA reviewer. Kiểm tra JSON mô tả sản phẩm và sửa lỗi. Chỉ trả về JSON."},
        {"role": "user", "content": prompt},
    ]

    raw = await _groq_call(messages, model=REVIEWER_MODEL, max_tokens=4000, temperature=0.2)
    reviewed = _extract_json(raw)

    if not reviewed:
        logger.warning("[ULTRA-AGENT] ⚠️ Review failed to parse — keeping original")
        reviewed = desc_data

    # Final force-inject (absolute guardrail)
    reviewed["ten_san_pham"] = state["product_name"]
    reviewed["gioi_tinh"] = state["gender_label"]
    reviewed["do_tuoi"] = state["age_label"]
    # chat_lieu: always use DB extraction if available — reviewer cannot revert it
    if mat_from_db:
        reviewed["chat_lieu"] = mat_from_db

    logger.info("[ULTRA-AGENT] ✅ Review done: ten_san_pham='%s'", reviewed.get("ten_san_pham"))
    return {**state, "reviewed_data": reviewed, "step": "reviewed"}


# ══════════════════════════════════════════════════════════════
# Node 5: Save to DB
# ══════════════════════════════════════════════════════════════
async def node_save(state: AgentState) -> AgentState:
    if state.get("error"):
        return state

    data = state["reviewed_data"] or state["description_data"]
    if not data:
        return {**state, "error": "No description data to save", "step": "save_failed"}

    # Build clean_description
    clean_parts = [
        data.get("ten_san_pham", ""),
        data.get("tagline", ""),
        data.get("mo_ta_chinh", ""),
        data.get("chat_lieu", ""),
        data.get("tinh_nang_vai", ""),
        data.get("dip_mac", ""),
        data.get("phoi_do", ""),
        data.get("hook_quang_cao", ""),
    ]
    clean_description = " | ".join(p for p in clean_parts if p)

    magento_code = state.get("magento_ref_code") or state["internal_ref_code"]
    base_code = state["internal_ref_code"]

    try:
        # UltraDescriptionDB.save() is a classmethod — call directly
        UltraDescriptionDB.save(
            internal_ref_code=base_code,
            magento_ref_code=magento_code,
            base_ref_code=base_code,
            product_name=state["product_name"],
            product_image_url=state["image_url"],
            product_line=state["product_line"],
            description_data=data,
            phase="grounded_v2",
            clean_description=clean_description,
        )
        logger.info("[ULTRA-AGENT] 💾 Saved: %s / magento=%s", base_code, magento_code)
        return {**state, "step": "saved"}
    except Exception as e:
        logger.error("[ULTRA-AGENT] ❌ Save failed: %s", e, exc_info=True)
        return {**state, "error": str(e), "step": "save_failed"}



# ══════════════════════════════════════════════════════════════
# Router
# ══════════════════════════════════════════════════════════════
def route_after_fetch(state: AgentState) -> str:
    if state.get("error"):
        return "end_error"
    return "vision"

def route_after_vision(state: AgentState) -> str:
    if state.get("error"):
        return "end_error"
    return "write"

def route_after_write(state: AgentState) -> str:
    if state.get("error"):
        return "end_error"
    return "size_guide"

def route_after_size_guide(state: AgentState) -> str:
    if state.get("error"):
        return "end_error"
    return "review"

def route_after_review(state: AgentState) -> str:
    if state.get("error"):
        return "end_error"
    return "save"


# ══════════════════════════════════════════════════════════════
# Build Graph
# ══════════════════════════════════════════════════════════════
def _build_graph() -> Any:
    g = StateGraph(AgentState)
    g.add_node("fetch", node_fetch_product)
    g.add_node("vision", node_vision)
    g.add_node("write", node_write)
    g.add_node("size_guide", node_size_guide)   # NEW: generate luu_y_size from real size data
    g.add_node("review", node_review)
    g.add_node("save", node_save)

    g.set_entry_point("fetch")

    g.add_conditional_edges("fetch",      route_after_fetch,      {"vision":      "vision",     "end_error": END})
    g.add_conditional_edges("vision",     route_after_vision,     {"write":       "write",      "end_error": END})
    g.add_conditional_edges("write",      route_after_write,      {"size_guide":  "size_guide", "end_error": END})
    g.add_conditional_edges("size_guide", route_after_size_guide, {"review":      "review",     "end_error": END})
    g.add_conditional_edges("review",     route_after_review,     {"save":        "save",       "end_error": END})
    g.add_edge("save", END)

    return g.compile()

_graph = _build_graph()


# ══════════════════════════════════════════════════════════════
# Public API
# ══════════════════════════════════════════════════════════════
async def run_ultra_description_agent(
    internal_ref_code: str,
    magento_ref_code: str | None = None,
) -> dict:
    """
    Run the full grounded description pipeline.
    Returns dict with keys: status, description_data, rendered_text, error
    """
    t0 = time.time()
    logger.info("[ULTRA-AGENT] 🚀 START: %s / %s", internal_ref_code, magento_ref_code)

    initial: AgentState = {
        "internal_ref_code": internal_ref_code,
        "magento_ref_code": magento_ref_code,
        "product_name": "",
        "product_line": "",
        "gender_raw": "unisex",
        "gender_label": "Unisex (Nam & Nữ)",
        "age_raw": "adult",
        "age_label": "Nam nữ từ 18 tuổi trở lên",
        "colors": "",
        "color_count": 1,
        "size_scale": "S, M, L, XL",
        "size_scale_arr": [],
        "sizeguide_content": "",
        "quantity_sold": 0,
        "description_text": "",
        "image_url": "",
        "image_base64": None,
        "vision_result": None,
        "description_data": None,
        "reviewed_data": None,
        "error": None,
        "step": "init",
    }

    final_state = await _graph.ainvoke(initial)
    elapsed = round(time.time() - t0, 1)

    if final_state.get("error"):
        logger.error("[ULTRA-AGENT] ❌ FAILED (%ss): %s", elapsed, final_state["error"])
        return {"status": "error", "message": final_state["error"], "elapsed_s": elapsed}

    result_data = final_state.get("reviewed_data") or final_state.get("description_data") or {}
    logger.info("[ULTRA-AGENT] ✅ DONE (%ss): %s", elapsed, final_state.get("product_name"))

    return {
        "status": "success",
        "internal_ref_code": internal_ref_code,
        "magento_ref_code": magento_ref_code,
        "product_name": final_state.get("product_name"),
        "description_data": result_data,
        "elapsed_s": elapsed,
    }
