# -*- coding: utf-8 -*-
from pathlib import Path
import math

from PIL import Image, ImageDraw, ImageFont


BASE_DIR = Path(__file__).resolve().parent
OUT = BASE_DIR / "MESCommonBase流程图.png"

W, H = 5200, 3800
img = Image.new("RGB", (W, H), "#F7F9FC")
draw = ImageDraw.Draw(img)

FONT_CANDIDATES = [
    r"C:\Windows\Fonts\NotoSansSC-VF.ttf",
    r"C:\Windows\Fonts\simhei.ttf",
    r"C:\Windows\Fonts\Deng.ttf",
    r"C:\Windows\Fonts\simsun.ttc",
]


def load_font(size, bold=False):
    candidates = []
    if bold:
        candidates.extend([r"C:\Windows\Fonts\Dengb.ttf", r"C:\Windows\Fonts\simhei.ttf"])
    candidates.extend(FONT_CANDIDATES)
    for path in candidates:
        try:
            return ImageFont.truetype(path, size=size)
        except Exception:
            pass
    return ImageFont.load_default()


font_title = load_font(72, True)
font_subtitle = load_font(34)
font_box = load_font(31)
font_box_small = load_font(27)
font_panel_title = load_font(42, True)
font_note = load_font(28)
font_edge = load_font(26)


def text_size(text, font):
    bbox = draw.textbbox((0, 0), text, font=font)
    return bbox[2] - bbox[0], bbox[3] - bbox[1]


def wrap_line(line, font, max_width):
    if not line:
        return [""]
    lines = []
    current = ""
    for ch in line:
        test = current + ch
        if text_size(test, font)[0] <= max_width or not current:
            current = test
        else:
            lines.append(current)
            current = ch
    if current:
        lines.append(current)
    return lines


def wrap_text(text, font, max_width):
    lines = []
    for raw in text.split("\n"):
        lines.extend(wrap_line(raw, font, max_width))
    return lines


def draw_multiline_center(text, cx, cy, max_width, font, fill="#172033", line_gap=12):
    lines = wrap_text(text, font, max_width)
    heights = [text_size(line, font)[1] for line in lines]
    total_h = sum(heights) + line_gap * (len(lines) - 1)
    y = cy - total_h / 2
    for line, h in zip(lines, heights):
        width, _ = text_size(line, font)
        draw.text((cx - width / 2, y), line, font=font, fill=fill)
        y += h + line_gap


def draw_multiline_left(text, x, y, max_width, font, fill="#172033", line_gap=10):
    lines = wrap_text(text, font, max_width)
    yy = y
    for line in lines:
        draw.text((x, yy), line, font=font, fill=fill)
        yy += text_size(line, font)[1] + line_gap
    return yy


def box_bounds(cx, cy, w, h):
    return (cx - w / 2, cy - h / 2, cx + w / 2, cy + h / 2)


def draw_box(cx, cy, w, h, text, fill="#FFFFFF", outline="#2E5A8A", width=5, font=None, radius=34):
    font = font or font_box
    bounds = box_bounds(cx, cy, w, h)
    draw.rounded_rectangle(bounds, radius=radius, fill=fill, outline=outline, width=width)
    draw_multiline_center(text, cx, cy, w - 70, font)
    return bounds


def draw_diamond(cx, cy, w, h, text, fill="#FFF4CC", outline="#A66F00", width=5, font=None):
    font = font or font_box
    points = [(cx, cy - h / 2), (cx + w / 2, cy), (cx, cy + h / 2), (cx - w / 2, cy)]
    draw.polygon(points, fill=fill, outline=outline)
    for i in range(width):
        offset = i * 0.9
        border_points = [
            (cx, cy - h / 2 + offset),
            (cx + w / 2 - offset, cy),
            (cx, cy + h / 2 - offset),
            (cx - w / 2 + offset, cy),
        ]
        draw.line(border_points + [border_points[0]], fill=outline, width=1)
    draw_multiline_center(text, cx, cy, w - 80, font)
    return points


def draw_panel(x, y, w, h, title, fill="#FFFFFF", outline="#B9C4D6"):
    draw.rounded_rectangle((x, y, x + w, y + h), radius=34, fill=fill, outline=outline, width=5)
    draw.text((x + 45, y + 28), title, font=font_panel_title, fill="#172033")


def arrow(points, color="#344054", width=6, label=None, label_pos=None):
    for p1, p2 in zip(points, points[1:]):
        draw.line((p1[0], p1[1], p2[0], p2[1]), fill=color, width=width)
    x1, y1 = points[-2]
    x2, y2 = points[-1]
    angle = math.atan2(y2 - y1, x2 - x1)
    size = 24
    left = (x2 - size * math.cos(angle - math.pi / 7), y2 - size * math.sin(angle - math.pi / 7))
    right = (x2 - size * math.cos(angle + math.pi / 7), y2 - size * math.sin(angle + math.pi / 7))
    draw.polygon([(x2, y2), left, right], fill=color)
    if label:
        lx, ly = label_pos if label_pos else ((x1 + x2) / 2, (y1 + y2) / 2)
        tw, th = text_size(label, font_edge)
        draw.rounded_rectangle(
            (lx - tw / 2 - 14, ly - th / 2 - 8, lx + tw / 2 + 14, ly + th / 2 + 8),
            radius=12,
            fill="#F7F9FC",
        )
        draw.text((lx - tw / 2, ly - th / 2), label, font=font_edge, fill=color)


def draw_title():
    main_title = "MESCommonBase.ashx 主流程图"
    subtitle = "入口解析 -> Type 分发 -> DataLink/ExcelWebCall 执行 -> 响应输出"
    tw, _ = text_size(main_title, font_title)
    draw.text(((W - tw) / 2, 45), main_title, font=font_title, fill="#111827")
    tw, _ = text_size(subtitle, font_subtitle)
    draw.text(((W - tw) / 2, 132), subtitle, font=font_subtitle, fill="#475467")


def draw_top_flow():
    cx = W / 2
    draw_box(cx, 285, 900, 125, "HTTP 请求\nMES_Manage/submit/MESCommonBase.ashx", "#E8F1FF", "#2864B4", font=font_box)
    draw_box(cx, 475, 1320, 145, "ProcessRequest\n设置 application/json，读取 Request.InputStream", "#E8F1FF", "#2864B4", font=font_box)
    draw_box(cx, 670, 1320, 145, "JsonMapper.ToObject(stream)\n尝试读取 Type / type", "#EAF7EF", "#227A45", font=font_box)
    draw_box(cx, 865, 1520, 145, "JavaScriptSerializer.Deserialize<jsonobj>(stream)\n成功后用 dataobj.Type 覆盖 type", "#EAF7EF", "#227A45", font=font_box)
    draw_diamond(cx, 1090, 520, 190, "dataobj\n== null?", "#FFF4CC", "#B27700", font=font_box)
    draw_box(3890, 1090, 840, 130, '读取 Request["param"]\n再次解析 Type / type', "#FFF7E6", "#B27700", font=font_box_small)
    draw_diamond(cx, 1315, 500, 180, "switch\n(type)", "#FFF4CC", "#B27700", font=font_box)

    arrow([(cx, 348), (cx, 402)])
    arrow([(cx, 548), (cx, 598)])
    arrow([(cx, 742), (cx, 792)])
    arrow([(cx, 938), (cx, 995)])
    arrow([(cx + 260, 1090), (3470, 1090)], label="是", label_pos=(3140, 1050))
    arrow([(3890, 1155), (3890, 1240), (2850, 1240), (2850, 1315)])
    arrow([(cx, 1185), (cx, 1225)], label="否", label_pos=(2555, 1210))


def draw_branch_panel():
    panel_x, panel_y, panel_w, panel_h = 160, 1460, 4880, 1120
    draw_panel(panel_x, panel_y, panel_w, panel_h, "Type 分发分支", fill="#FFFFFF", outline="#C7D2E5")
    arrow([(W / 2, 1405), (W / 2, panel_y + 10)])

    card_w, card_h = 1080, 330
    xs = [790, 1960, 3130, 4300]
    y1, y2 = 1740, 2205
    cards = [
        (xs[0], y1, "2001  Excel 导出\nExcelWebCall.ExcelFile(jsonData)\n生成 bytes / fileName / extension\n响应：二进制下载", "#EAF7EF", "#227A45"),
        (xs[1], y1, "2002  PDF 导出\nExcelWebCall.ExcelFilePdf(jsonData)\nExcel 转 PDF\n响应：二进制下载", "#EAF7EF", "#227A45"),
        (xs[2], y1, "2003  合成 Excel 图片/扩展名导出\n读取 Form 第一项 dataimg[0]\nExcelWebCall.ExcelFile(jsonData)\n响应：二进制下载", "#EAF7EF", "#227A45"),
        (xs[3], y1, "2004  数据库文件下载\nDataLink.ExePROCEDURE_Type2004\n返回 bytes / fileName / extension\n响应：二进制下载", "#EAF7EF", "#227A45"),
        (xs[0], y2, "15  文件上传\n读取 Request.Files[0]\n拆分 name / suffix / bytes\nDataLink.ExePROCEDURE_Type15\n响应：JSON", "#EEF2FF", "#4F46E5"),
        (xs[1], y2, "16  数据库文件下载\nDataLink.ExePROCEDURE_Type16\n返回 fileName / suffix / bytes\nbytes 为 null 时直接 return", "#EAF7EF", "#227A45"),
        (xs[2], y2, "4000  IP 查询\n读取 ServerVariables / UserHostAddress\n响应：文本 IP", "#FFF7E6", "#B27700"),
        (xs[3], y2, "default  通用业务调用\nDataLink.SqlWebCall(type,jsonData,dataobj)\n进入数据库/存储过程二次分发", "#F3E8FF", "#7E22CE"),
    ]
    for cx, cy, text, fill, outline in cards:
        draw_box(cx, cy, card_w, card_h, text, fill, outline, font=font_box_small, radius=28)

    return panel_x, panel_y, panel_w, panel_h, xs, y2, card_h


def draw_detail_panels(panel_info):
    panel_x, panel_y, panel_w, panel_h, xs, y2, card_h = panel_info
    resp_x, resp_y, resp_w, resp_h = 260, 2740, 2050, 740
    detail_x, detail_y, detail_w, detail_h = 2520, 2740, 2420, 740

    draw_panel(resp_x, resp_y, resp_w, resp_h, "响应输出", fill="#FFFFFF", outline="#C7D2E5")
    resp_text = (
        "文件分支 2001/2002/2003/2004/16：\n"
        "  application/octet-stream + Content-Disposition\n"
        "  BinaryWrite(bytes) + Flush/End\n\n"
        "Type 15 与 default：\n"
        "  Response.Write(JSON 文本)\n\n"
        "Type 4000：\n"
        "  Response.Write(userIP)\n\n"
        "外层 catch：异常时写当前 responseText，初始值为 NULL"
    )
    draw_multiline_left(resp_text, resp_x + 60, resp_y + 115, resp_w - 120, font_note, fill="#344054", line_gap=12)

    draw_panel(detail_x, detail_y, detail_w, detail_h, "DataLink.SqlWebCall 默认分支", fill="#FFFFFF", outline="#C7D2E5")
    detail_text = (
        "初始化：InitSystemReg 读取 Web.config 的 ConnectionString\n\n"
        "8888 / 5001 / 5002：加密、注册/改密、登录并生成 token\n\n"
        "1 / 2 / 5：旧协议存储过程，Param 为拼接字符串\n\n"
        "11 / 111 / 12 / 13 / 21：新协议存储过程，Param 为 JSON 数组字符串\n\n"
        "1001 / 1002 / 3 / 4 / 7 / 22 / 3001：直接 SQL、建表导入或 SQL 命令执行\n\n"
        "输出：数组 JSON 或 { code, message, data }"
    )
    draw_multiline_left(detail_text, detail_x + 60, detail_y + 115, detail_w - 120, font_note, fill="#344054", line_gap=12)

    arrow([(xs[3], y2 + card_h / 2), (xs[3], 2630), (detail_x + detail_w / 2, 2630), (detail_x + detail_w / 2, detail_y)], color="#7E22CE")
    arrow([(panel_x + 1150, panel_y + panel_h), (panel_x + 1150, resp_y)], color="#227A45")
    arrow([(detail_x, detail_y + detail_h / 2), (resp_x + resp_w, detail_y + detail_h / 2)], color="#7E22CE")

    return resp_x, resp_y, resp_w, resp_h


def draw_note_and_end(resp_panel):
    resp_x, resp_y, resp_w, resp_h = resp_panel
    note_x, note_y, note_w, note_h = 520, 3545, 4160, 150
    draw.rounded_rectangle((note_x, note_y, note_x + note_w, note_y + note_h), radius=30, fill="#FFF1F3", outline="#C01048", width=5)
    note = "关键注意：入口层缺少统一鉴权和白名单；客户端可通过 Type + Name/Param 触发 SQL 或存储过程能力，异常多数不记录日志。"
    draw_multiline_center(note, note_x + note_w / 2, note_y + note_h / 2, note_w - 120, font_note, fill="#7A271A", line_gap=8)

    end_x, end_y = W - 560, H - 125
    draw_box(end_x, end_y, 420, 95, "请求结束", "#E8F1FF", "#2864B4", font=font_box_small, radius=45)
    arrow([(resp_x + resp_w / 2, resp_y + resp_h), (resp_x + resp_w / 2, H - 125), (end_x - 210, H - 125)], color="#344054")

    footer = "源文件：working/MESCommonBase流程图.mmd    PNG：working/MESCommonBase流程图.png"
    fw, _ = text_size(footer, font_edge)
    draw.text(((W - fw) / 2, H - 45), footer, font=font_edge, fill="#667085")


draw_title()
draw_top_flow()
panel_info = draw_branch_panel()
resp_panel = draw_detail_panels(panel_info)
draw_note_and_end(resp_panel)

img.save(OUT, format="PNG", dpi=(220, 220), optimize=True)
print(OUT)
print(f"{W}x{H}")
