#!/usr/bin/env python3
"""
护照写入工具 - 直接写入 m03.png (已清除 8 个变量 + MRZ 文本)
- 不使用 LaMa，直接在 m03.png 上叠加写入
- 使用 mask-based rendering 绕过 Pillow 9.5.0 bug
"""

import os, json, sys, argparse
from datetime import datetime
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont

# ──────────────────────────────────────────────
# 1. 字体加载
# ──────────────────────────────────────────────
FONT_DIR = Path(__file__).parent.parent / "fonts"
FONT_FILES = {
    "sans":      "DejaVuSans.ttf",
    "sans_bold": "DejaVuSans-Bold.ttf",
    "mrz":       "OCR-B.otf",
}

def load_font(name: str, size: int) -> ImageFont.FreeTypeFont:
    path = FONT_DIR / FONT_FILES[name]
    if not path.exists():
        raise FileNotFoundError(f"字体不存在: {path}")
    return ImageFont.truetype(str(path), size)

# ──────────────────────────────────────────────
# 2. 坐标计算（基于 m03 OCR 结果）
# ──────────────────────────────────────────────
def calc_layout(W: int, H: int) -> dict:
    scale_f = min(W / 900, H / 630)
    sf = lambda v: max(1, int(v * scale_f))

    fs_title = max(12, int(24 * scale_f))
    fs_pass  = max(10, int(17 * scale_f))
    fs_label = max(8,  int(11 * scale_f))
    fs_val   = max(9,  int(13 * scale_f))
    fs_mrz   = max(10, int(18 * scale_f))
    fs_tiny  = max(6,  int(8  * scale_f))

    f_title = load_font("sans_bold", fs_title)
    f_pass  = load_font("sans_bold", fs_pass)
    f_label = load_font("sans",      fs_label)
    f_val   = load_font("sans_bold", fs_val)
    f_mrz   = load_font("mrz", fs_mrz)
    f_tiny  = load_font("sans",      fs_tiny)

    COL = {
        "title": "#1a1a1a", "pass": "#6B0000", "gold": "#C4A35A",
        "label": "#666", "val": "#111", "mrz": "#111",
    }

    title_y = sf(30)
    passport_y = sf(25)
    type_y = sf(45)

    c_l1 = sf(35); c_v1 = sf(170)
    c_l2 = sf(480); c_v2 = sf(610)
    ry = sf(360); gap = sf(28)

    sepy = ry + 6*gap + sf(5) + sf(60)
    mrzh = sf(14)

    return {
        "scale_f": scale_f, "sf": sf,
        "fonts": {"title": f_title, "pass": f_pass, "label": f_label,
                  "val": f_val, "mrz": f_mrz, "tiny": f_tiny},
        "colors": COL,
        "coords": {
            "title_y": title_y,
            "passport_y": passport_y,
            "type_y": type_y,
            "fields": {"c_l1": c_l1, "c_v1": c_v1,
                       "c_l2": c_l2, "c_v2": c_v2,
                       "ry": ry, "gap": gap},
            "mrz": {"sepy": sepy, "mrzh": mrzh,
                    "left": sf(20), "right": W - sf(20),
                    "center_x": W // 2,
                    "line1_y": sepy + mrzh,
                    "line2_y": sepy + mrzh + sf(24),
                    "prefix_x": sf(20), "prefix_y": sepy + mrzh - sf(6)},
        }
    }

# ──────────────────────────────────────────────
# 3. MRZ 生成
# ──────────────────────────────────────────────
sys.path.insert(0, str(Path(__file__).parent))
from mrz import build_line1, build_line2  # type: ignore

def gen_mrz(data: dict, dob: datetime, expiry: datetime) -> tuple[str, str]:
    dob_str = dob.strftime("%y%m%d")
    exp_str = expiry.strftime("%y%m%d")
    l1 = build_line1(data["surname"], data["given_name"],
                     data.get("type", "PV"), data.get("country_code", "MMR"))
    l2 = build_line2(data["passport_no"], dob_str, exp_str,
                     data.get("sex", "M"), "MMR")
    assert len(l1) == 44 and len(l2) == 44, f"MRZ长度异常: {len(l1)}/{len(l2)}"
    return l1, l2

# ──────────────────────────────────────────────
# 4. Mask-based 绘制（核心修复）
# ──────────────────────────────────────────────
def hex_to_rgb(hex_color: str) -> tuple:
    h = hex_color.lstrip('#')
    if len(h) == 3: h = ''.join(c*2 for c in h)
    return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))

def draw_text_mask(draw: ImageDraw.ImageDraw, img: Image.Image, xy, text, font, fill, anchor, debug=False, label=""):
    mask = font.getmask(text, mode='L')
    mask_img = Image.frombytes('L', mask.size, bytes(mask))
    mw, mh = mask_img.size
    x, y = xy
    
    anchors = {
        "lt": (x, y), "mt": (x - mw//2, y), "rt": (x - mw, y),
        "mm": (x - mw//2, y - mh//2), "lb": (x, y - mh),
        "mb": (x - mw//2, y - mh), "rb": (x - mw, y - mh)
    }
    tl_x, tl_y = anchors.get(anchor, (x, y))
    
    text_color = hex_to_rgb(fill) if isinstance(fill, str) else fill
    text_layer = Image.new('RGB', (mw, mh), text_color)
    img.paste(text_layer, (tl_x, tl_y), mask_img)
    
    if debug:
        draw.rectangle([tl_x, tl_y, tl_x + mw, tl_y + mh], outline="#FF0000", width=2)
        draw.ellipse([x-3, y-3, x+3, y+3], fill="#00FF00")
        debug_font = load_font("sans", 12)
        draw.text((tl_x, tl_y - 14), f"{label} {mw}x{mh}", fill="#FF0000", font=debug_font)

def write_m03_passport(img_path: str, out_path: str, data: dict, debug: bool = False):
    """在 m03.png 上写入 8 个变量 + 4 个固定值 + MRZ"""
    img = Image.open(img_path).convert("RGB")
    W, H = img.size
    L = calc_layout(W, H)
    sf = L["sf"]
    F = L["fonts"]
    C = L["colors"]
    XY = L["coords"]

    draw = ImageDraw.Draw(img)

    # ── 1. 页眉/固定标题 (已经在 m03.png 中，仅做确认) ──
    # 不需要写入，因为已经在模板中
    
    # ── 2. 右上固定字段 (已在模板中，但我们重新写入以确保清晰) ──
    # Type / PV
    draw_text_mask(draw, img, (W - sf(30), XY["type_y"]), 
                  f"Type  {data.get('type','PV')}    Code  {data.get('country_code','MMR')}",
                  F["tiny"], "#555", "rt", debug, "type_code")
    
    # Passport No
    draw_text_mask(draw, img, (W - sf(30), XY["passport_y"]), 
                  f"Passport No  {data['passport_no']}",
                  F["label"], "#1a1a1a", "rt", debug, "passport_no")
    
    # ── 3. 固定字段 (4个) - 已经在模板中，我们重新写入 ──
    # Type (单独)
    draw_text_mask(draw, img, (626, 1065), data.get("type", "PV"), 
                  F["val"], C["val"], "lt", debug, "type_val")
    # Country Code
    draw_text_mask(draw, img, (784, 1066), data.get("country_code", "MMR"), 
                  F["val"], C["val"], "lt", debug, "country_val")
    # Nationality
    draw_text_mask(draw, img, (600, 1200), data.get("nationality", "MYANMAR"), 
                  F["val"], C["val"], "lt", debug, "nationality_val")
    # Authority
    draw_text_mask(draw, img, (1091, 1397), data.get("authority", "MOHA, KYAINGTONG"), 
                  F["val"], C["val"], "lt", debug, "authority_val")

    # ── 4. 可编辑字段 (8个) - 这是用户清除的区域，我们写入新值 ──
    fields = [
        ("surname", "Surname / Nom", "given_name", "Given Name / Prenoms"),
        ("nationality", "Nationality", None, ""),
        ("dob", "Date of birth", "sex", "Sex / Sexe"),
        ("birth_place", "Place of birth", None, ""),
        ("issue_date", "Date of issue", "expiry_date", "Date of expiry"),
        ("authority", "Authority / Autorite", None, ""),
    ]
    ry = XY["fields"]["ry"]
    gap = XY["fields"]["gap"]
    for i, (k1, lab1, k2, lab2) in enumerate(fields):
        y = ry + i * gap
        # 左列标签
        draw_text_mask(draw, img, (XY["fields"]["c_l1"], y), lab1, F["label"], C["label"], "lt", debug, f"L{i}_lab")
        # 左列值 (k1)
        if k1:
            draw_text_mask(draw, img, (XY["fields"]["c_v1"], y), str(data.get(k1, "")), F["val"], C["val"], "lt", debug, f"L{i}_val")
        # 右列标签
        if k2:
            draw_text_mask(draw, img, (XY["fields"]["c_l2"], y), lab2, F["label"], C["label"], "lt", debug, f"R{i}_lab")
        # 右列值 (k2)
        if k2:
            draw_text_mask(draw, img, (XY["fields"]["c_v2"], y), str(data.get(k2, "")), F["val"], C["val"], "lt", debug, f"R{i}_val")

    # ── 5. MRZ ── (用户清除的区域)
    dob = datetime.strptime(data["dob"], "%d %b %Y")
    expiry = datetime.strptime(data["expiry_date"], "%d %b %Y")
    l1, l2 = gen_mrz(data, dob, expiry)

    mrz = XY["mrz"]
    draw_text_mask(draw, img, (mrz["prefix_x"], mrz["prefix_y"]), "P<", F["mrz"], C["mrz"], "lt", debug, "mrz_prefix")
    draw_text_mask(draw, img, (mrz["center_x"], mrz["line1_y"]), l1, F["mrz"], C["mrz"], "mt", debug, "mrz_l1")
    draw_text_mask(draw, img, (mrz["center_x"], mrz["line2_y"]), l2, F["mrz"], C["mrz"], "mt", debug, "mrz_l2")

    # ── 保存 ──
    img.save(out_path, dpi=(300, 300))
    print(f"✓ 写入完成: {out_path} ({W}x{H})")
    if debug:
        dbg_path = out_path.replace(".png", "_debug.png")
        img.save(dbg_path)
        print(f"✓ 调试图: {dbg_path}")

# ──────────────────────────────────────────────
# 5. CLI
# ──────────────────────────────────────────────
def parse_args():
    p = argparse.ArgumentParser(description="直接写入 m03.png (需先清除 8 变量 + MRZ)")
    p.add_argument("--img", required=True, help="输入图片 (默认: /data/passport/input/m03.png)")
    p.add_argument("--out", required=True, help="输出图片")
    p.add_argument("--data", required=True, help="JSON 字符串或 @文件路径")
    p.add_argument("--debug", action="store_true", help="生成调试图")
    return p.parse_args()

def load_data(arg: str) -> dict:
    if arg.startswith("@"):
        with open(arg[1:], "r", encoding="utf-8") as f:
            return json.load(f)
    return json.loads(arg)

if __name__ == "__main__":
    args = parse_args()
    data = load_data(args.data)

    required = ["surname", "given_name", "nationality", "dob", "sex",
                "birth_place", "issue_date", "expiry_date", "authority",
                "passport_no", "type", "country_code"]
    missing = [k for k in required if k not in data]
    if missing:
        sys.exit(f"❌ 缺少字段: {missing}")

    # 如果没有指定 img，使用默认 m03.png
    img_path = args.img
    if not img_path:
        img_path = "/data/passport/input/m03.png"

    write_m03_passport(img_path, args.out, data, args.debug)