"""
Fashion Matches — API Route
Manages AI-powered fashion matching (ai_matches) per product.

Endpoints:
  GET  /api/fashion-matches/{code}              → lấy ai_matches
  POST /api/fashion-matches/{code}/update       → lưu manual edits
  POST /api/fashion-matches/{code}/regen        → trigger AI engine 1 SP
  POST /api/fashion-matches/batch               → trigger engine toàn bộ
  GET  /api/fashion-matches/batch/status        → poll batch progress
  GET  /api/fashion-matches/rules/config        → lấy fashion_rules.json
  PUT  /api/fashion-matches/rules/config        → cập nhật rules
  GET  /api/fashion-matches/rules/meta          → structured metadata (colors, styles, occasions)
  POST /api/fashion-matches/color-logic         → kiểm tra synergy 2 màu
  POST /api/fashion-matches/outfit-suggest      → gợi ý outfit đầy đủ từ 1 product code
  POST /api/fashion-matches/score-test          → test pairwise score breakdown
"""

import json
import logging
import os
from typing import Optional

from fastapi import APIRouter, BackgroundTasks, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel

from common.pool_wrapper import get_pooled_connection_compat

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/fashion-matches", tags=["Fashion Matches"])

TABLE = "dashboard_canifa.ultra_descriptions"
RULES_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "worker", "fashion_rules.json")


# ─── Pydantic ────────────────────────────────────────────────
class UpdateMatchesRequest(BaseModel):
    ai_matches: dict


class UpdateRulesRequest(BaseModel):
    rules: dict


class OutfitSuggestRequest(BaseModel):
    code: str
    occasion: Optional[str] = None


class ColorLogicRequest(BaseModel):
    src_color: str
    tgt_color: str


# ─── Helpers ─────────────────────────────────────────────────
def _get_ai_matches(code: str) -> Optional[dict]:
    conn = None
    try:
        conn = get_pooled_connection_compat()
        cur = conn.cursor()
        cur.execute(
            f"SELECT ai_matches FROM {TABLE} WHERE magento_ref_code = %s OR internal_ref_code = %s LIMIT 1",
            (code, code),
        )
        row = cur.fetchone()
        cur.close()
        if not row:
            return None
        data = row[0]
        if isinstance(data, str):
            data = json.loads(data)
        return data or {}
    except Exception as e:
        logger.error("[FashionMatches] get error %s: %s", code, e)
        return None
    finally:
        if conn:
            conn.close()


def _save_ai_matches(code: str, ai_matches: dict) -> bool:
    conn = None
    try:
        conn = get_pooled_connection_compat()
        cur = conn.cursor()
        cur.execute(
            f"""UPDATE {TABLE}
                SET ai_matches = %s::jsonb, updated_at = NOW()
                WHERE magento_ref_code = %s OR internal_ref_code = %s""",
            (json.dumps(ai_matches, ensure_ascii=False), code, code),
        )
        updated = cur.rowcount > 0
        conn.commit()
        cur.close()
        return updated
    except Exception as e:
        if conn:
            conn.rollback()
        logger.error("[FashionMatches] save error %s: %s", code, e)
        return False
    finally:
        if conn:
            conn.close()


def _run_engine_background(code: Optional[str] = None):
    try:
        from worker.stylist_engine import StylistEngine
        engine = StylistEngine()
        if code:
            engine.run_for_code(code)
        else:
            engine.run_batch()
    except Exception as e:
        logger.error("[FashionMatches] engine error: %s", e)


def _load_rules() -> dict:
    with open(RULES_PATH, "r", encoding="utf-8") as f:
        return json.load(f)


def _detect_color_key(color_str: str, color_keys: dict) -> Optional[str]:
    c = color_str.lower()
    for key, variants in color_keys.items():
        if any(v in c for v in variants):
            return key
    return None


def _color_group_explain(src_key: Optional[str], tgt_key: Optional[str], rules: dict) -> dict:
    color_groups = rules.get("color_groups", {})
    group_matrix = rules.get("color_group_matrix", {})
    color_matrix = rules.get("color_matrix", {})
    default_score = rules.get("default_color_score", 12)

    src_group = color_groups.get(src_key) if src_key else None
    tgt_group = color_groups.get(tgt_key) if tgt_key else None

    if src_group and tgt_group:
        score = group_matrix.get(src_group, {}).get(tgt_group, default_score)
        advice = _color_advice(src_group, tgt_group, src_key, tgt_key)
        return {
            "src_key": src_key, "src_group": src_group,
            "tgt_key": tgt_key, "tgt_group": tgt_group,
            "score": score, "max": 30,
            "method": "group_matrix",
            "advice": advice,
        }

    if src_key and tgt_key:
        score = color_matrix.get(src_key, {}).get(tgt_key, default_score)
        return {
            "src_key": src_key, "src_group": src_group or "?",
            "tgt_key": tgt_key, "tgt_group": tgt_group or "?",
            "score": score, "max": 30,
            "method": "color_matrix",
            "advice": f"Phối {src_key} + {tgt_key} (legacy matrix)",
        }

    return {
        "src_key": src_key or "?", "src_group": src_group or "?",
        "tgt_key": tgt_key or "?", "tgt_group": tgt_group or "?",
        "score": default_score, "max": 30,
        "method": "default",
        "advice": "Không xác định được màu — dùng điểm mặc định",
    }


def _color_advice(src_group: str, tgt_group: str, src_key: Optional[str], tgt_key: Optional[str]) -> str:
    combo = (src_group, tgt_group)
    if combo == ("neutral", "neutral"):
        return f"✅ Công thức An Toàn: {src_key} + {tgt_key} — luôn hài hòa, phù hợp mọi dịp."
    if combo in [("neutral", "light"), ("light", "neutral")]:
        return f"✅ Công thức Điểm Nhấn: màu sáng ({src_key or tgt_key}) + trung tính ({tgt_key or src_key}) — nổi bật, tươi trẻ."
    if combo in [("neutral", "dark"), ("dark", "neutral")]:
        return f"✅ Công thức Điểm Nhấn: màu nổi ({src_key or tgt_key}) + trung tính ({tgt_key or src_key}) — cá tính, ấn tượng."
    if combo in [("light", "dark"), ("dark", "light")]:
        return f"⚠️ Tương phản mạnh: {src_key} + {tgt_key} — táo bạo, cần phụ kiện trung tính để cân bằng."
    if combo == ("light", "light"):
        return f"⚠️ Cùng tông sáng: {src_key} + {tgt_key} — pastel dịu dàng nhưng dễ bị nhạt, cần phụ kiện tương phản."
    if combo == ("dark", "dark"):
        return f"⚠️ Cùng tông đậm: {src_key} + {tgt_key} — mạnh mẽ, cần chú ý không bị nặng nề. Thêm phụ kiện trung tính."
    return f"Phối {src_key} + {tgt_key}"


def _build_color_strategy(src_key: Optional[str], src_group: Optional[str], rules: dict) -> dict:
    group_matrix = rules.get("color_group_matrix", {})
    color_groups_map = rules.get("color_groups", {})

    if not src_group:
        return {"summary": "Không xác định được màu sắc sản phẩm.", "strategies": []}

    if src_group == "neutral":
        summary = f"{src_key} là màu Trung Tính — dễ phối nhất, áp dụng được cả 3 công thức."
        strategies = [
            {
                "name": '🟢 Công thức "An Toàn"',
                "desc": "Phối với màu Neutral khác (Trắng, Đen, Xám, Be, Nâu) — luôn hài hòa.",
                "score": group_matrix.get("neutral", {}).get("neutral", 30),
                "example_colors": _get_colors_by_group("neutral", color_groups_map, exclude=src_key),
            },
            {
                "name": '✨ Công thức "Điểm Nhấn" (sáng)',
                "desc": "Phối với màu Light (Hồng, Vàng, Xanh lam, Tím) — tươi trẻ, nổi bật.",
                "score": group_matrix.get("neutral", {}).get("light", 22),
                "example_colors": _get_colors_by_group("light", color_groups_map),
            },
            {
                "name": '🔥 Công thức "Cá Tính" (đậm)',
                "desc": "Phối với màu Dark (Đỏ, Cam, Xanh Jeans, Xanh navy) — mạnh mẽ, ấn tượng.",
                "score": group_matrix.get("neutral", {}).get("dark", 25),
                "example_colors": _get_colors_by_group("dark", color_groups_map),
            },
        ]
    elif src_group == "light":
        summary = f"{src_key} là màu Sáng/Pastel — phối BOTTOM Neutral để tiết chế, tránh phụ kiện quá sặc sỡ."
        strategies = [
            {
                "name": "✅ Phối với Neutral",
                "desc": f"Màu {src_key} (sáng/pastel) → phối BOTTOM Neutral (Trắng, Đen, Xám, Be) để cân bằng.",
                "score": group_matrix.get("light", {}).get("neutral", 22),
                "example_colors": _get_colors_by_group("neutral", color_groups_map),
            },
            {
                "name": "⚠️ Phụ kiện nên Neutral",
                "desc": "Chọn phụ kiện đen/trắng để không rối mắt. Tuân thủ quy tắc tối đa 3 màu.",
                "score": None,
                "example_colors": ["Đen", "Trắng", "Xám"],
            },
        ]
    else:
        summary = f"{src_key} là màu Đậm/Nổi — ưu tiên BOTTOM Neutral để cân bằng. Phụ kiện cũng nên Neutral."
        strategies = [
            {
                "name": "✅ Phối với Neutral",
                "desc": f"Màu {src_key} (đậm/nổi) → phối BOTTOM Neutral (Trắng, Đen, Xám, Be) để hài hòa.",
                "score": group_matrix.get("dark", {}).get("neutral", 20),
                "example_colors": _get_colors_by_group("neutral", color_groups_map),
            },
            {
                "name": "🎯 Mono-color (cùng tông)",
                "desc": f"Mặc head-to-toe {src_key} — mạnh mẽ, cần phụ kiện kim loại Gold/Silver làm điểm nhấn.",
                "score": group_matrix.get("dark", {}).get("dark", 10),
                "example_colors": _get_colors_by_group("dark", color_groups_map, exclude=src_key),
            },
        ]

    return {"summary": summary, "strategies": strategies}


def _get_colors_by_group(group: str, color_groups_map: dict, exclude: Optional[str] = None) -> list:
    return [k for k, g in color_groups_map.items() if g == group and k != exclude]


# ─── Endpoints ───────────────────────────────────────────────

@router.get("/{code}")
async def get_fashion_matches(code: str):
    from worker.stylist_engine import StylistEngine
    engine = StylistEngine()
    data = engine.compute_dynamic_rule_matches(code)
    classifications = engine.compute_super_classifications_sql(code)
    
    # Return empty ai_matches if not found (or gracefully handle) to avoid UI breaking
    if data is None:
        data = {}
    return {"ok": True, "code": code, "ai_matches": data, "classifications": classifications}


@router.post("/{code}/update")
async def update_fashion_matches(code: str, req: UpdateMatchesRequest):
    ok = _save_ai_matches(code, req.ai_matches)
    if ok:
        logger.info("[FashionMatches] Manual update saved: %s", code)
        return {"ok": True, "message": "Đã lưu gợi ý phối đồ"}
    return JSONResponse({"ok": False, "error": "Không tìm thấy sản phẩm"}, status_code=404)


@router.post("/{code}/regen")
async def regen_fashion_matches(code: str):
    import asyncio
    await asyncio.to_thread(_run_engine_background, code)
    logger.info("[FashionMatches] Regen finished: %s", code)
    return {"ok": True, "message": f"Đã tính toán phối đồ cho {code}"}

@router.post("/batch-regen")
async def batch_regen_fashion_matches(request: Request):
    data = await request.json()
    codes = data.get("codes", [])
    if not codes:
        return {"ok": True, "message": "None"}
    
    def _run_multiple():
        from worker.stylist_engine import StylistEngine
        engine = StylistEngine()
        for c in codes:
            try:
                engine.run_for_code(c)
            except:
                pass

    import asyncio
    await asyncio.to_thread(_run_multiple)
    return {"ok": True, "message": f"Đã xong {len(codes)} sp"}


@router.post("/batch")
async def batch_fashion_matches(background_tasks: BackgroundTasks):
    from worker.stylist_engine import BATCH_STATE
    if BATCH_STATE.get("is_running"):
        return JSONResponse({"ok": False, "error": "Batch đang chạy, vui lòng chờ"}, status_code=409)
    background_tasks.add_task(_run_engine_background, None)
    return {"ok": True, "message": "Đã khởi động batch Stylist Engine..."}


@router.get("/batch/status")
async def batch_status():
    from worker.stylist_engine import BATCH_STATE
    state = dict(BATCH_STATE)
    pct = 0
    if state.get("total", 0) > 0:
        pct = round(state["done"] / state["total"] * 100, 1)
    state["progress_pct"] = pct
    return {"ok": True, "state": state}


@router.get("/rules/config")
async def get_rules():
    try:
        rules = _load_rules()
        return {"ok": True, "rules": rules}
    except Exception as e:
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


@router.put("/rules/config")
async def update_rules(req: UpdateRulesRequest):
    try:
        with open(RULES_PATH, "w", encoding="utf-8") as f:
            json.dump(req.rules, f, ensure_ascii=False, indent=2)
        return {"ok": True, "message": "Rules đã được cập nhật"}
    except Exception as e:
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


@router.get("/rules/meta")
async def get_rules_meta():
    try:
        rules = _load_rules()
        color_hex_map = {
            "Trắng": "#F5F5F5", "Đen": "#1A1A1A", "Be": "#D4B896", "Xám": "#9E9E9E",
            "Nâu": "#795548", "Vàng": "#FDD835", "Hồng": "#F48FB1", "Xanh lam": "#64B5F6",
            "Tím": "#BA68C8", "Đỏ": "#EF5350", "Cam": "#FFA726", "Xanh lá": "#66BB6A",
            "Xanh navy": "#1A237E", "Xanh Jeans": "#5C6BC0", "Xanh than": "#00897B",
        }
        color_meta = []
        for key, variants in rules.get("color_keys", {}).items():
            group = rules.get("color_groups", {}).get(key, "unknown")
            color_meta.append({
                "key": key, "group": group,
                "hex": color_hex_map.get(key, "#CCCCCC"),
                "keywords": variants,
            })
        return {
            "ok": True,
            "meta": {
                "colors": color_meta,
                "color_group_matrix": rules.get("color_group_matrix", {}),
                "styles": list(rules.get("style_compat", {}).keys()),
                "occasions": [
                    {"key": k, "label": v}
                    for k, v in rules.get("occasion_labels", {}).items()
                ],
                "roles": [
                    {"key": k, "label": v}
                    for k, v in rules.get("role_labels", {}).items()
                ],
                "score_weights": rules.get("score_weights", {}),
                "version": rules.get("version", "?"),
            },
        }
    except Exception as e:
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


@router.post("/color-logic")
async def color_logic(req: ColorLogicRequest):
    try:
        rules = _load_rules()
        color_keys = rules.get("color_keys", {})
        src_key = _detect_color_key(req.src_color, color_keys)
        tgt_key = _detect_color_key(req.tgt_color, color_keys)
        result = _color_group_explain(src_key, tgt_key, rules)
        return {"ok": True, "result": result}
    except Exception as e:
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


@router.post("/outfit-suggest")
async def outfit_suggest(req: OutfitSuggestRequest):
    try:
        rules = _load_rules()
        color_keys = rules.get("color_keys", {})

        from worker.stylist_engine import StylistEngine
        engine = StylistEngine()
        catalog = engine._load_catalog()

        # Find source product in catalog
        src_product = next((p for p in catalog if p.get("code") == req.code or p.get("internal_ref_code") == req.code), None)

        if not src_product:
            return JSONResponse(
                {"ok": False, "error": f"Không tìm thấy sản phẩm: {req.code}"},
                status_code=404,
            )

        logger.info("[OutfitSuggest] Computing real-time dynamic matches for %s", req.code)
        ai_matches = engine.compute_dynamic_rule_matches(req.code)

        src_color = src_product.get("color", "")
        src_key = _detect_color_key(src_color, color_keys)
        src_group = rules.get("color_groups", {}).get(src_key) if src_key else None
        
        desc_data = src_product.get("description_data", {})
        src_name = src_product.get("name", "")
        src_line = src_product.get("product_line", "")
        src_gender = src_product.get("gender", "")
        src_img = src_product.get("image", "")

        src_color = src_color or ""
        src_key = _detect_color_key(src_color, color_keys)
        src_group = rules.get("color_groups", {}).get(src_key) if src_key else None

        occasions = rules.get("occasions", [])
        active_occasions = [req.occasion] if (req.occasion and req.occasion in occasions) else occasions

        outfit_by_occasion = {}
        for occ in active_occasions:
            occ_data = ai_matches.get(occ, {})
            if not occ_data:
                continue
            slots = {}
            for role, items in occ_data.items():
                enriched = []
                for item in items:
                    item_color_key = _detect_color_key(item.get("color", ""), color_keys)
                    color_info = _color_group_explain(src_key, item_color_key, rules)
                    enriched.append({
                        **item,
                        "color_key": item_color_key,
                        "color_group": rules.get("color_groups", {}).get(item_color_key, "?") if item_color_key else "?",
                        "color_synergy": color_info,
                    })
                slots[role] = enriched
            if slots:
                outfit_by_occasion[occ] = {
                    "label": rules.get("occasion_labels", {}).get(occ, occ),
                    "slots": slots,
                }

        color_strategy = _build_color_strategy(src_key, src_group, rules)
        
        # Inject our brand new SQL-based super classification matches
        classifications = engine.compute_super_classifications_sql(req.code)

        return {
            "ok": True,
            "source_product": {
                "code": req.code,
                "name": src_name or "",
                "color": src_color,
                "color_key": src_key,
                "color_group": src_group,
                "product_line": src_line or "",
                "gender": src_gender or "",
                "image": src_img or "",
                "style": desc_data.get("phong_cach", ""),
                "occasion": desc_data.get("dip_mac", ""),
                "material": desc_data.get("vat_lieu", ""),
            },
            "color_strategy": color_strategy,
            "outfit_by_occasion": outfit_by_occasion,
            "classifications": classifications
        }

    except Exception as e:
        logger.error("[OutfitSuggest] error: %s", e, exc_info=True)
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


class ScoreTestRequest(BaseModel):
    source_code: str
    target_code: str


@router.post("/score-test")
async def score_test(req: ScoreTestRequest):
    try:
        from worker.stylist_engine import StylistEngine
        engine = StylistEngine()
        catalog = engine._load_catalog()

        source = next((p for p in catalog if p.get("code") == req.source_code), None)
        target = next((p for p in catalog if p.get("code") == req.target_code), None)

        if not source:
            return JSONResponse({"ok": False, "error": f"Không tìm thấy SP nguồn: {req.source_code}"}, status_code=404)
        if not target:
            return JSONResponse({"ok": False, "error": f"Không tìm thấy SP đích: {req.target_code}"}, status_code=404)

        breakdown = engine._score_breakdown(source, target)
        total = sum(v["score"] for v in breakdown.values())
        occ0 = (engine.rules.get("occasions") or ["hang_ngay"])[0]
        reason = engine._build_reason(source, target, occ0, total)

        return {
            "ok": True,
            "result": {
                "source_code": req.source_code,
                "target_code": req.target_code,
                "target_name": target.get("name", ""),
                "roles_str": target.get("product_line", ""),
                "total_score": total,
                "reason": reason,
                "breakdown": breakdown,
            },
        }
    except Exception as e:
        logger.error("[ScoreTest] error: %s", e)
        return JSONResponse({"ok": False, "error": str(e)}, status_code=500)


# --- Rules Framework HTML View ---
@router.get("/rules/view")
async def rules_view():
    from fastapi.responses import HTMLResponse
    from collections import defaultdict
    rows = []
    conn = None
    try:
        conn = get_pooled_connection_compat()
        cur = conn.cursor()
        cur.execute("""
            SELECT gender_target, anchor_category, occasion_tag, match_role, target_category, ai_reason
            FROM dashboard_canifa.chatbot_fashion_rules
            WHERE gender_target != 'all'
            ORDER BY CASE gender_target
                WHEN 'nu' THEN 1 WHEN 'nam' THEN 2 WHEN 'unisex' THEN 3
                WHEN 'be_gai' THEN 4 WHEN 'be_trai' THEN 5 ELSE 6
            END, occasion_tag, match_role, anchor_category
        """)
        rows = cur.fetchall()
        cur.close()
    except Exception as e:
        logger.error("[RulesView] DB error: %s", e)
    finally:
        if conn: conn.close()
    grouped = defaultdict(lambda: defaultdict(list))
    for gender, anchor, occ, role, target, reason in rows:
        grouped[gender][occ].append({"anchor": anchor, "role": role, "target": target, "reason": reason or ""})
    GENDER_META = {
        "nu":      {"label": "NGA Nu",  "color": "#ec4899", "bg": "#fdf2f8", "group": "Neutral+Light+Dark",   "colors": "Trang, Hong, Be, Tim, Xanh lam, Den"},
        "nam":     {"label": "NAM Nam", "color": "#3b82f6", "bg": "#eff6ff", "group": "Neutral (+5 boost)",   "colors": "Be, Xam, Den, Trang, Nau, Xanh than"},
        "unisex":  {"label": "UNI Unisex","color": "#8b5cf6","bg": "#f5f3ff","group": "Neutral+Dark",          "colors": "Den, Trang, Xam, Do, Xanh navy, Xanh Jeans"},
        "be_gai":  {"label": "BG Be Gai","color": "#f43f5e","bg": "#fff1f2","group": "Light Pastel (+5 boost)","colors": "Hong, Tim, Vang nhat, Xanh lam, Trang"},
        "be_trai": {"label": "BT Be Trai","color": "#f59e0b","bg": "#fffbeb","group": "Dark (+5 boost)",       "colors": "Vang, Cam, Xanh Jeans, Xanh navy, Do"},
    }
    OCC_LABELS = {"hang_ngay":"Hang ngay","di_lam":"Di lam","di_choi":"Di choi","du_lich":"Du lich","the_thao":"The thao","mac_nha":"Mac nha"}
    ROLE_ICONS = {"top":"TOP","bottom":"BTM","outerwear":"OTR","accessory":"ACC"}
    sections = ""
    for gk, meta in GENDER_META.items():
        occ_data = grouped.get(gk, {})
        if not occ_data: continue
        rows_html = ""
        for occ, rlist in occ_data.items():
            items = "".join(
                f'<span class="ri" title="{r["reason"]}">{r["anchor"]} &rarr; {r["target"]} <em>({ROLE_ICONS.get(r["role"],r["role"])})</em></span>'
                for r in rlist)
            rows_html += f'<tr><td class="oc"><b>{OCC_LABELS.get(occ,occ)}</b></td><td class="rc">{items}</td></tr>'
        sections += f'<div class="gs" style="--c:{meta["color"]};--bg:{meta["bg"]}"><div class="gh"><span class="gtitle">{meta["label"]}</span><span class="gchip">{meta["group"]}</span><span class="gcol">{meta["colors"]}</span></div><table class="rt"><thead><tr><th>Dip mac</th><th>TOP Anchor &rarr; TARGET (role) theo product_line DB</th></tr></thead><tbody>{rows_html}</tbody></table></div>'
    html = f"""<!DOCTYPE html><html lang="vi"><head><meta charset="UTF-8"><title>Framework Phoi Do</title><style>
*{{box-sizing:border-box;margin:0;padding:0}}body{{font-family:-apple-system,sans-serif;background:#f8fafc;color:#1e293b;padding:20px}}
.ph{{max-width:1000px;margin:0 auto 20px;padding-bottom:16px;border-bottom:2px solid #e2e8f0}}
.ptitle{{font-size:20px;font-weight:800}}.psub{{font-size:12px;color:#64748b;margin-top:3px}}
.chips{{display:flex;gap:8px;margin-top:8px}}.chip{{background:#f1f5f9;border:1px solid #e2e8f0;border-radius:20px;padding:3px 11px;font-size:12px;font-weight:600;color:#475569}}
.gs{{max-width:1000px;margin:0 auto 16px;border-radius:12px;overflow:hidden;border:1.5px solid var(--c);box-shadow:0 2px 8px rgba(0,0,0,.06)}}
.gh{{background:var(--bg);border-bottom:1.5px solid var(--c);padding:10px 16px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}}
.gtitle{{font-size:15px;font-weight:800;color:var(--c)}}.gchip{{background:var(--c);color:#fff;border-radius:20px;padding:2px 8px;font-size:11px;font-weight:700}}.gcol{{font-size:11px;color:#64748b}}
.rt{{width:100%;border-collapse:collapse;background:#fff}}
.rt th{{text-align:left;padding:8px 14px;font-size:10px;text-transform:uppercase;color:#94a3b8;background:#f8fafc;border-bottom:1px solid #e2e8f0}}
.rt tr:not(:last-child) td{{border-bottom:1px solid #f8fafc}}.rt td{{padding:7px 14px;vertical-align:top}}
.oc{{width:110px;font-size:12px;font-weight:700;color:#334155}}.rc{{display:flex;flex-wrap:wrap;gap:5px}}
.ri{{display:inline-flex;align-items:center;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:7px;padding:3px 9px;font-size:12px;font-weight:600;cursor:help;transition:.15s}}
.ri:hover{{background:var(--bg);border-color:var(--c)}}.ri em{{font-size:10px;color:#94a3b8;font-style:normal;margin-left:4px}}
</style></head><body>
<div class="ph"><div class="ptitle">📐 Framework Phối Đồ — Canifa AI Stylist v1.0</div>
<div class="psub">Lưới logic Giới tính × Dịp → nguồn: <code>chatbot_fashion_rules.gender_target</code></div>
<div class="chips"><span class="chip">{len(rows)} rules</span><span class="chip">{len(GENDER_META)} demographic</span></div></div>
{sections}</body></html>"""
    return HTMLResponse(content=html)
