"""商品上传模板导出：从 db 读 SPU/SKU → 调 template_router 路由填入上传模板 Excel。

流程（product_node 生成产品图后调用）：
  1. 从 spu_sku.db 读 SPU（款号）+ 该款选定颜色的全部尺码 SKU；
  2. 用 model/template_router.py 的 TemplateRouter：
       - insert 一行 SPU（SPU货号 + 商品属性字段）
       - 每个尺码 insert 一行 SKU（路由到 SPU 行下方，SKU货号 = 款号-颜色编码-尺码，填尺码表）
       - 商品轮播图1~N 填生成的产品图路径（底图/印花/模特/合成）
  3. save 输出 <模板名>_已填写.xlsx 到指定目录。

字段映射：db 字段名 → 上传模板列名（见 SPU_MAP / SKU_MAP）。
"""
import re
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional

from graph.product import _connect

# 商品轮播图列名关键词（模板存在中/英/日变体，如 商品轮播图1 / Product Carousel Image 1 / 商品カルーセル画像1）
_CAROUSEL_KW = ("轮播", "carousel", "カルーセル")


def _size_rank(size) -> tuple:
    """把尺码字符串转成可排序 rank（从小到大）。

    优先级：数字码(90/100...) < 字母码(XS/S/M/L/XL/XXL/3XL...) < 童装年龄码(0/3M、1-2Y...)
            < 尺寸规格(30*40...) < 未知(字典序兜底) < 均码/Onesize(最后)。
    """
    s = str(size or "").strip().upper()
    if not s:
        return (9, 0, "")
    if s in ("ONESIZE", "ONE SIZE", "FREESIZE", "FREE SIZE", "均码"):
        return (8, 0, s)
    if s == "XS":
        return (2, 0, s)
    if s in ("S", "M", "L"):
        return (2, {"S": 1, "M": 2, "L": 3}[s], s)
    m = re.fullmatch(r"(X+)(L)", s)          # XL/XXL/XXXL/XXXXL/XXXXXL
    if m:
        return (2, 3 + len(m.group(1)), s)
    m = re.fullmatch(r"(\d+)XL", s)          # 3XL/4XL/5XL
    if m:
        return (2, 3 + int(m.group(1)), s)
    if s.isdigit():                          # 数字码：90/100/110...
        return (1, int(s), s)
    m = re.fullmatch(r"(\d+)\s*[-/]\s*(\d+)\s*(?:M|Y|YRS|YR)?", s)   # 童装年龄码
    if m:
        return (3, (int(m.group(1)) + int(m.group(2))) / 2.0, s)
    m = re.search(r"(\d+(?:\.\d+)?)\s*(?:CM)?\s*[X*]", s)            # 尺寸规格：30*40/12CM X 12CM
    if m:
        return (4, float(m.group(1)), s)
    return (5, 0, s)                         # 未知格式：字典序兜底


def _carousel_col(router, idx: int) -> Optional[int]:
    """定位「商品轮播图{idx}」列号：先精确匹配（中文列名），失败则按中/英/日关键词模糊匹配序号。"""
    try:
        return router.resolve_col(f"商品轮播图{idx}")
    except KeyError:
        pass
    for name, col in router.column_map.items():
        low = str(name).lower().replace(" ", "").replace("　", "")
        if not any(k in low for k in _CAROUSEL_KW):
            continue
        m = re.search(r"(\d+)$", low)
        if m and int(m.group(1)) == idx:
            return col
    return None


def _detail_col(router) -> Optional[int]:
    """定位「详情图文」列：优先英语（详情图文-英语），回退日语（详情图文-日语），再回退任意详情图文。"""
    for name in ("详情图文-英语", "详情图文-英文", "详情图文-EN"):
        try:
            return router.resolve_col(name)
        except KeyError:
            pass
    try:
        return router.resolve_col("详情图文-日语")
    except KeyError:
        pass
    for name, col in router.column_map.items():
        if "详情图文" in str(name):
            return col
    return None


def _ja_col(router) -> Optional[int]:
    """定位「日语名称」列（基础信息组，如 日语名称/日语标题）。"""
    try:
        return router.resolve_col("日语名称")
    except KeyError:
        pass
    for name, col in router.column_map.items():
        low = str(name)
        if "日语" in low and "详情图文" not in low and "轮播图" not in low and "名称" in low:
            return col
    return None


def _fill_design_fields(router, spu_code: str, oss_code: str, cn_title: str, en_title: str,
                        ja_title: str, composite_by_sku: Dict[str, Any],
                        all_composite_urls: List[str], seed_shot_urls: List[str],
                        only_rows: Optional[List[int]] = None) -> None:
    """按用户要求填充设计联动字段：
    - SPU 行：SPU货号=设计货号、SKU货号=设计货号、商品名称=cn_title、英文名称=en_title、
      日语名称=ja_title、商品轮播图1=随机一张三合一主图、详情图文=全部主图+种草图链接 | 分割
    - SKU 行：SPU货号=设计货号、SKU货号=该颜色货号、商品轮播图1=该颜色三合一链接、
      商品名称/英文名称/日语名称 与 SPU 一致
    only_rows：合并模式下只填充本产品块的行（None=该款全部行）
    """
    import random
    try:
        color_col = router.resolve_col("色值(主规格)")
    except KeyError:
        color_col = None
    try:
        spu_col = router.resolve_col("SPU货号")
    except KeyError:
        spu_col = None
    try:
        name_col = router.resolve_col("商品名称")
    except KeyError:
        name_col = None
    try:
        en_col = router.resolve_col("英文名称")
    except KeyError:
        en_col = None
    try:
        ja_col = _ja_col(router)
    except KeyError:
        ja_col = None
    try:
        sku_code_col = router.resolve_col("SKU货号")
    except KeyError:
        sku_code_col = None
    car1 = _carousel_col(router, 1)
    detail_col = _detail_col(router)

    for row in router.find_spu_rows(spu_code):
        if only_rows is not None and row not in only_rows:
            continue   # 合并模式：只填本产品块的行
        lvl = str(router.ws.cell(row, router.level_col).value or "").strip().lower()
        color = str(router.ws.cell(row, color_col).value or "").strip() if color_col else ""
        if lvl == "spu":
            if spu_col and oss_code:
                router.ws.cell(row, spu_col, oss_code)
            if sku_code_col and oss_code:
                router.ws.cell(row, sku_code_col, oss_code)
            if name_col and cn_title:
                router.ws.cell(row, name_col, cn_title)
            if en_col and en_title:
                router.ws.cell(row, en_col, en_title)
            if ja_col and ja_title:
                router.ws.cell(row, ja_col, ja_title)
            if car1 is not None and all_composite_urls:
                router.ws.cell(row, car1, random.choice(all_composite_urls))   # SPU 轮播图1 随机
            if detail_col is not None:
                links = [u for u in (all_composite_urls + list(seed_shot_urls or [])) if u]
                if links:
                    router.ws.cell(row, detail_col, "|".join(links))          # 详情图文 | 分割
        else:
            if spu_col and oss_code:
                router.ws.cell(row, spu_col, oss_code)
            # SKU 行与 SPU 一致：商品名称/英文名称/日语名称
            if name_col and cn_title:
                router.ws.cell(row, name_col, cn_title)
            if en_col and en_title:
                router.ws.cell(row, en_col, en_title)
            if ja_col and ja_title:
                router.ws.cell(row, ja_col, ja_title)
            cc = composite_by_sku.get(color) or composite_by_sku.get("")      # 按色值匹配该颜色主图
            if sku_code_col and oss_code:
                router.ws.cell(row, sku_code_col, oss_code)                   # SKU货号=SPU货号（同一货号）
            if car1 is not None and cc and cc.get("url"):
                router.ws.cell(row, car1, cc["url"])                          # 该颜色轮播图1


def _build_spu_row(spu: Dict[str, Any], spu_code: str, origin_province: str,
                   color: Optional[str] = None) -> Dict[str, Any]:
    """构造一行 SPU（固定字段：SKC货号=code、风格=休闲、商品产地=经营站点；多颜色时用色值列区分）。"""
    row: Dict[str, Any] = {
        "基础信息-商品层级": "spu",
        "SKC货号": spu_code,        # code 路由为 SKC货号（用户要求）
        "风格": "休闲",              # style 路由为"休闲"（用户要求）
        "商品产地": origin_province,  # 产地省份不用填，经营站点填到「商品产地」
    }
    if color:
        row["色值(主规格)"] = color
    for dbk, header in SPU_MAP.items():
        v = spu.get(dbk)
        if v not in (None, ""):
            row[header] = v
    return row


def _find_price_header(router) -> str:
    """定位价格列表头：任意含「申报价格」的列（美站/日站/英站…模糊匹配）；找不到回退默认。"""
    for k in router.column_map:
        if "申报价格" in str(k):
            return str(k)
    return "申报价格-日本站"


def _build_sku_row(spu_code: str, sc: str, sk: Dict[str, Any], size: str, color: str,
                   warehouses: List[str], markup_percent: float = 0.0,
                   multi: bool = True, price_header: str = "申报价格-日本站") -> Dict[str, Any]:
    """构造一行 SKU（固定字段：SPU货号、SKC货号=sku.code、规格类型2、币种 CNY、发货仓1~N 及库存 200）。
    价格（price_header 列，如 申报价格-美国站/日本站，模糊匹配）= SKU.price × (1+markup/100)，预先填好。
    规格类型2 统一填「尺码」两个字（不是 size 参数值）。"""
    row: Dict[str, Any] = {
        "基础信息-商品层级": "sku",
        "SPU货号": spu_code,
        "SKC货号": sk.get("code") or sc,   # SKC货号 = SKU 的 code（款号-颜色编码）
        "色值(主规格)": color,
        "规格类型2": "尺码",                # 规格类型2 统一填「尺码」（不填 size 值）
        "币种": "CNY",
    }
    for j, w in enumerate(warehouses, start=1):
        row[f"发货仓{j}"] = w
        row[f"发货仓{j}库存"] = 200
    for dbk, header in SKU_MAP.items():
        if dbk == "color":
            continue
        if dbk == "price":
            header = price_header   # 模糊匹配的实际价格列（申报价格-美站/日站/英站…）
        v = sk.get(dbk)
        if dbk == "price" and v not in (None, ""):
            v = round(float(v) * (1 + markup_percent / 100), 2)   # 申报价格 = price × (1+加价%)
        if v not in (None, ""):
            row[header] = v
    return row


def _fill_sku_carousel(router, spu_code: str, color: str, color_col: int,
                       first: Dict[str, Any], sku_imgs: List[str]) -> None:
    """SKU 行商品轮播图2~5：db img_url_2~5 优先，无则回退生成图；按色值列匹配所属 SKU 行。"""
    for j in range(2, 6):
        url = first.get(f"img_url_{j}")            # db CDN url（该颜色 SKU 的 img_url_2~5）
        img = url if url not in (None, "") else (sku_imgs[j - 2] if j - 2 < len(sku_imgs) else None)
        if not img:
            continue
        col = _carousel_col(router, j)
        if col is None:
            continue
        for row in router.find_spu_rows(spu_code):
            if (str(router.ws.cell(row, router.level_col).value or "").strip().lower() == "sku"
                    and str(router.ws.cell(row, color_col).value or "").strip() == color):
                router.ws.cell(row, col, str(img))

# db SPU 字段 -> 上传模板列名
SPU_MAP: Dict[str, str] = {
    "code": "SPU货号",
    "material": "材质",
    "component_1": "成分1",
    "component_proportion_1": "成分1成分比例",
    "component_2": "成分2",
    "component_proportion_2": "成分2成分比例",
    "component_3": "成分3",
    "component_proportion_3": "成分3成分比例",
    "pattern": "图案",
    "details": "细节",
    "collar_style": "领型",
    "care_Instructions": "护理说明",
    "fabric": "面料",
    "target_audience": "适用人群",
    "season": "季节",
    "is_transparent": "是否透明",
    "layout": "版型",
    "weaving_method": "织造方式",
    "printing_type": "印花类型",
    "fabric_texture_1": "面料纹理1",
    "fabric_weight_1": "面料克重1（g/m²)",
    "fabric_weight_unit_1": "面料克重1（g/m²)单位",
    "lining_texture": "里料纹理",
}

# db SKU 字段 -> 上传模板列名
SKU_MAP: Dict[str, str] = {
    "color": "色值(主规格)",
    "size": "尺码",
    "size_group": "尺码组别",
    "size_type": "尺码类型",
    "shoulder_width": "肩宽（cm）",
    "bust": "胸围全围（cm）",
    "clothing_length": "衣长（cm）",
    "sleeve_length": "袖长（cm）",
    "longest_side": "最长边（cm）",
    "secondary_long_side": "次长边（cm）",
    "shortest_side": "最短边（cm）",
    "package_weight": "重量（g）",   # 包装重量：从 db 提取
    "price": "申报价格-日本站",
}


def _read_spu(db_path, spu_code: str) -> Optional[Dict[str, Any]]:
    conn = _connect(db_path)
    row = conn.execute("SELECT * FROM SPU WHERE code = ?", (spu_code,)).fetchone()
    conn.close()
    return dict(row) if row else None


def _read_meta(router) -> tuple:
    """读模板顶头元信息：经营站点、发货仓（按标签名定位，不依赖固定行列）。

    返回 (origin_province, warehouses)：
      - origin_province：经营站点去掉末尾「站」（如「日本站」→「日本」）
      - warehouses：发货仓按「、」分隔的列表（如「名古屋仓、inkreach——东京」→ 2 个）
    """
    ws = router.ws
    top = max(1, router.group_row - 1)   # 元信息区位于分组行之前

    def _val(label: str) -> str:
        for row in range(1, top + 1):
            for col in range(1, min(ws.max_column, 30) + 1):
                if str(ws.cell(row, col).value or "").strip() == label:
                    return str(ws.cell(row + 1, col).value or "").strip()
        return ""

    site = _val("经营站点")
    raw = _val("发货仓")
    if not site and not raw:
        # 回退：旧版固定位置（第2行第1/2列）
        site = str(ws.cell(2, 1).value or "").strip()
        raw = str(ws.cell(2, 2).value or "").strip()
    origin_province = site[:-1] if site.endswith("站") else site
    warehouses = [w.strip() for w in raw.split("、") if w.strip()]
    return origin_province, warehouses


def _read_skus(db_path, spu_code: str, sku_code: str) -> List[Dict[str, Any]]:
    """该款该颜色的全部尺码 SKU，按尺码从小到大排序（字母码/数字码/童装码/规格码）。"""
    conn = _connect(db_path)
    rows = conn.execute(
        """SELECT s.*, p.code AS spu_code FROM SKU s
           JOIN SPU p ON s.spu_id = p.id
           WHERE p.code = ? AND s.code = ?""", (spu_code, sku_code)).fetchall()
    conn.close()
    skus = [dict(r) for r in rows]
    skus.sort(key=lambda sk: _size_rank(sk.get("size")))
    return skus


def export_product(
    db_path,
    spu_code: str,
    sku_code,  # str | List[str]：单颜色或多个颜色
    template_dir: str,
    template_path: str,
    out_path: str,
    images: Optional[List[str]] = None,
    spu_per_color: bool = True,
    oss_code: str = "",
    cn_title: str = "",
    en_title: str = "",
    ja_title: str = "",
    composite_urls: Optional[List[Dict[str, Any]]] = None,
    seed_shot_urls: Optional[List[str]] = None,
    append_to: str = "",
    markup_percent: float = 0.0,
) -> Path:
    """生成商品上传"已填写"模板（支持多产品合并到同一文件）。

    sku_code       ：SKU 颜色编码，支持单个 str 或多个（list/tuple/逗号分隔字符串）。
    spu_per_color  ：True（默认）= 每个颜色导出一个 SPU 块；False = 单 SPU 下挂所有颜色 SKU 变体。
    template_dir   ：template_router.py 所在目录（用于 import）
    template_path  ：商品上传模版 xlsx 路径
    images         ：生成的产品图路径列表（仅作用于第一个颜色块：SPU 行轮播图1 + SKU 行回退）
    oss_code       ：设计货号（前缀+3位计数），SPU货号/SKU货号 列均填它
    cn_title       ：商品名称（中文标题）
    en_title       ：英文名称（英文标题）
    ja_title       ：日语名称（日语标题，JP 模板生成）
    composite_urls：[{"sku_code","color","url","code"}] 每色三合一主图（含图床链接与货号）
    seed_shot_urls ：种草图图床链接列表（详情图文 | 拼接用）
    append_to      ：已有输出文件路径；提供则在其基础上追加本产品块（一次任务多产品合并一个模板）
    markup_percent ：加价百分比，申报价格 = SKU.price × (1+markup/100) 预填
    返回输出文件路径。
    """
    # 1) 读 db（支持单/多颜色）
    spu = _read_spu(db_path, spu_code)
    if spu is None:
        raise ValueError(f"SPU {spu_code} 不存在于 db")
    if isinstance(sku_code, str) and "," in sku_code:
        sku_codes = [s.strip() for s in sku_code.split(",") if s.strip()]
    elif isinstance(sku_code, (list, tuple)):
        sku_codes = list(sku_code)
    else:
        sku_codes = [sku_code]
    skus_by_color: List[tuple] = []
    for sc in sku_codes:
        skus = _read_skus(db_path, spu_code, sc)
        if not skus:
            raise ValueError(f"SKU {sc} 不存在于 db（款号 {spu_code}）")
        skus_by_color.append((sc, skus))
    images = [str(i) for i in (images or []) if i]

    # 2) import template_router（优先 config 的 template_dir；打包后回退 _MEIPASS/model）
    tdir = Path(template_dir)
    candidates = [tdir]
    meipass = getattr(sys, "_MEIPASS", None)
    if meipass:
        candidates.append(Path(meipass) / "model")
    # 用户上传的模板可能在任意目录（无 template_router.py），兜底项目自带 templates/
    from graph.paths import project_root as _proj_root
    candidates.append(_proj_root() / "templates")
    for d in candidates:
        if d.exists() and str(d) not in sys.path:
            sys.path.insert(0, str(d))
    from template_router import TemplateRouter  # noqa: E402

    # append_to：合并模式从已有输出文件继续追加（一次任务多产品填一个模板）
    router = TemplateRouter(append_to if append_to else template_path)
    try:
        origin_province, warehouses = _read_meta(router)
        price_header = _find_price_header(router)   # 申报价格列（美站/日站/英站…模糊匹配）
        multi = len(skus_by_color) > 1
        color_col = router.resolve_col("色值(主规格)")
        block_rows: List[int] = []   # 本产品块插入的所有行号（_fill_design_fields 只填这些行）

        if spu_per_color:
            # 3) 单 SPU 多色：1 个 SPU 行（无色值，SPU 级信息由 _fill_design_fields 填充）
            #    + 全部颜色尺码 SKU 行（色值在 SKU 行区分）
            block_rows.append(router.insert(
                _build_spu_row(spu, spu_code, origin_province),
                match="exact",
            ))
            for ci, (sc, skus) in enumerate(skus_by_color):
                first = skus[0]
                color = first.get("color") or sc

                # 该颜色全部尺码 SKU（SKU 行 SPU货号/SKU货号=spu_code，色值区分）
                for i, sk in enumerate(skus):
                    size = sk.get("size") or f"{i+1}"
                    block_rows.append(router.insert(
                        _build_sku_row(spu_code, sc, sk, size, color, warehouses,
                                       markup_percent=markup_percent, multi=True,
                                       price_header=price_header),
                        spu_code=spu_code, match="exact",
                    ))

                # 3.3) 轮播图：首色 SKU 行轮播图1 = 生成首图；SKU 行按色值填 db url/生成图
                if ci == 0 and images:
                    col1 = _carousel_col(router, 1)
                    if col1 is not None:
                        sku_rows = router.find_sku_rows(spu_code)
                        if sku_rows:
                            router.ws.cell(min(sku_rows), col1, str(images[0]))
                sku_imgs = [x for x in (images[1:] + images[:1]) if x][:4] if (ci == 0 and images) else []
                _fill_sku_carousel(router, spu_code, color, color_col, first, sku_imgs)
        else:
            # 4) 单 SPU + 多颜色变体：1 个 SPU 行（无色值）+ 所有颜色所有尺码 SKU 行（色值区分）
            block_rows.append(router.insert(
                _build_spu_row(spu, spu_code, origin_province), match="exact"))
            multi_variant = len(skus_by_color) > 1
            for ci, (sc, skus) in enumerate(skus_by_color):
                first = skus[0]
                color = first.get("color") or sc
                for i, sk in enumerate(skus):
                    size = sk.get("size") or f"{i+1}"
                    block_rows.append(router.insert(
                        _build_sku_row(spu_code, sc, sk, size, color, warehouses,
                                       markup_percent=markup_percent, multi=multi_variant,
                                       price_header=price_header),
                    ))
                sku_imgs = [x for x in (images[1:] + images[:1]) if x][:4] if (ci == 0 and images) else []
                _fill_sku_carousel(router, spu_code, color, color_col, first, sku_imgs)
            # 无 SPU 行：首图（轮播图1）由 _fill_design_fields 按 SKU 行填充

        # 5) 设计联动字段：货号/标题/轮播图路由/详情图文（图床链接，| 分割）
        if oss_code or cn_title or en_title or ja_title or composite_urls:
            by_sku: Dict[str, Any] = {}
            all_urls: List[str] = []
            for cc in (composite_urls or []):
                if cc.get("color"):
                    by_sku[str(cc["color"]).strip()] = cc
                if cc.get("url"):
                    all_urls.append(str(cc["url"]))
            _fill_design_fields(router, spu_code, oss_code, cn_title, en_title, ja_title,
                                by_sku, all_urls, seed_shot_urls or [], only_rows=block_rows)

        out = router.save(out_path)
        return Path(out)
    finally:
        try:
            router.close()
        except Exception:
            pass
