# -*- coding: utf-8 -*-
"""
render_quote.py：渲染引擎 — data + 模板路径 -> docx。

从 reports4 移植，做了两处通用化改造:
1. 模板路径参数化: render(data, out_path, template_path)
2. 图片字段配置驱动: 读 meta.json 的 image_fields 声明，不再写死字段名
"""
import copy
import os
import ssl
import sys
import tempfile
import urllib.request
import json

try:
    sys.stdout.reconfigure(encoding="utf-8")
except (AttributeError, OSError):
    pass

from docxtpl import DocxTemplate, InlineImage
from docx.shared import Mm


def _resolve_image_path(src, tmp_files):
    """把图片字段值解析为本地文件路径。

    下载成功的临时文件会记录到 tmp_files，由调用方在渲染结束后统一清理。
    """
    def _download(url):
        try:
            ctx = ssl.create_default_context()
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
            data = urllib.request.urlopen(req, context=ctx, timeout=30).read()
            ext = os.path.splitext(url.split("?")[0])[1] or ".png"
            fd, path = tempfile.mkstemp(suffix=ext)
            os.write(fd, data)
            os.close(fd)
            tmp_files.append(path)
            return path
        except Exception as e:
            print(f"[warn] 下载图片失败({url}): {e}")
            return None

    if src is None:
        return ""
    if src == "":
        return ""
    if isinstance(src, str) and src.lower().startswith(("http://", "https://")):
        return _download(src) or ""
    if os.path.exists(src):
        return src
    print(f"[warn] 找不到图片({src})，跳过")
    return ""


def _make_inline_image(tpl, src, width_mm, tmp_files):
    """创建 InlineImage；src 为 "" 或 None 时不创建（返回 None）。"""
    resolved = _resolve_image_path(src, tmp_files)
    if resolved == "":
        return None
    return InlineImage(tpl, resolved, width=Mm(width_mm))


def _resolve_field_path(data, path):
    """根据字段路径提取所有目标值。

    支持: "root_field" / "list[].field" / "list[].field[]"
    """
    results = []
    parts = path.split(".")

    if len(parts) == 1:
        key = parts[0]
        if key in data:
            results.append((data, key))
        return results

    # 嵌套列表: "list[].field[]" —— 必须先于单层判断，否则会被 parts[0] 分支误捕获
    if len(parts) == 2 and "[]" in parts[1]:
        list_name = parts[0].replace("[]", "")
        field = parts[1].replace("[]", "")
        for item in data.get(list_name, []):
            lst = item.get(field, [])
            if isinstance(lst, list):
                for idx in range(len(lst)):
                    results.append((lst, idx))
        return results

    if len(parts) == 2 and "[]" in parts[0]:
        list_name = parts[0].replace("[]", "")
        field = parts[1]
        for item in data.get(list_name, []):
            if field in item:
                results.append((item, field))
        return results

    if len(parts) == 2:
        list_name = parts[0]
        field = parts[1]
        for item in data.get(list_name, []):
            if field in item:
                results.append((item, field))
        return results

    return results


def _fill_images_from_meta(tpl, data, meta, tmp_files):
    """根据 meta.json 中的 image_fields 声明填充所有图片字段。"""
    image_fields = meta.get("image_fields", {})
    for path, width_meta in image_fields.items():
        width_mm = width_meta.get("width_mm", 50)
        refs = _resolve_field_path(data, path)
        for container, key in refs:
            val = container[key]
            if val is None:
                container[key] = ""
            elif isinstance(val, list):
                container[key] = [
                    _make_inline_image(tpl, x, width_mm, tmp_files) if x is not None else None
                    for x in val
                ]
                container[key] = [img for img in container[key] if img is not None]
            else:
                img = _make_inline_image(tpl, val, width_mm, tmp_files)
                container[key] = img if img else ""
    return data


def render(data, out_path, template_path, meta=None):
    """渲染报价文档。"""
    if meta is None:
        meta_dir = os.path.dirname(template_path)
        meta_file = os.path.join(meta_dir, "meta.json")
        if os.path.isfile(meta_file):
            with open(meta_file, "r", encoding="utf-8") as f:
                meta = json.load(f)
        else:
            meta = {}

    # 深拷贝：避免修改调用方传入的数据，同时把字符串字段替换成 InlineImage
    data = copy.deepcopy(dict(data))
    tpl = DocxTemplate(template_path)

    tmp_files = []
    try:
        data = _fill_images_from_meta(tpl, data, meta, tmp_files)
        tpl.render(data)
        tpl.save(out_path)
    finally:
        for p in tmp_files:
            try:
                os.remove(p)
            except OSError:
                pass

    return out_path
