"""
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
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, background_tasks: BackgroundTasks):
    background_tasks.add_task(_run_engine_background, code)
    logger.info("[FashionMatches] Regen triggered: %s", code)
    return {"ok": True, "message": f"Đang tính toán phối đồ cho {code}..."}


@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)
