#!/usr/bin/env python3
"""
Fixed version of write_lazy.py with correct m03 positions
Fixes the coordinate misalignment issue where issue_date/expiry_date 
were 170px too far right and passport number wasn't overwritten.
"""
import json, sys, argparse
from datetime import datetime
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import cv2

FONT_DIR = Path(__file__).parent.parent / "fonts"
FONT_FILES = {"sans": "DejaVuSans.ttf", "sans_bold": "DejaVuSans-Bold.ttf", "mono": "DejaVuSansMono.ttf"}

def load_font(name, size):
    path = FONT_DIR / FONT_FILES[name]
    if not path.exists():
        raise FileNotFoundError(f"Font not found: {path}")
    return ImageFont.truetype(str(path), size)

sys.path.insert(0, str(Path(__file__).parent))
from mrz import build_line1, build_line2

def gen_mrz(data, dob, expiry):
    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

# Fixed m03 positions - these write in the CORRECT blank fields
# Key fix: issue_date/expiry_date moved left by ~170px from original m02-mapped positions
# passport_no now properly overwritten at x=1600
M03_FIELDS = {
    "surname":     {"x": 780, "y": 1045},
    "given_name":  {"x": 1080, "y": 1060},
    "sex":         {"x": 630, "y": 1105},
    "dob":         {"x": 650, "y": 1175},
    "birth_place": {"x": 660, "y": 1235},
    "issue_date":  {"x": 630, "y": 1300},
    "expiry_date": {"x": 630, "y": 1380},
    "authority":   {"x": 1010, "y": 1395},
    "passport_no": {"x": 1600, "y": 50},
    "nationality": {"x": 600, "y": 1200},
}

def hex_to_rgb(h):
    h = h.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, img, 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
    anchor_map = {"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 = anchor_map.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")

def write_passport_lazy_fixed(img_path, out_path, data, debug=False, skip_header=False):
    img = Image.open(img_path).convert("RGB")
    W, H = img.size
    arr = np.array(img.convert('L'))
    draw = ImageDraw.Draw(img)
    
    # Find reference points from template
    refs = {}
    pass_region = arr[1000:1100, 250:600]
    _, binary = cv2.threshold(pass_region, 100, 255, cv2.THRESH_BINARY_INV)
    contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours:
        largest = max(contours, key=cv2.contourArea)
        x, y, w, h = cv2.boundingRect(largest)
        refs["passport"] = (250 + x + w//2, 1000 + y + h//2)
    else:
        refs["passport"] = (432, 1063)
    
    moha_region = arr[1380:1450, 900:1300]
    _, binary_m = cv2.threshold(moha_region, 100, 255, cv2.THRESH_BINARY_INV)
    contours_m, _ = cv2.findContours(binary_m, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours_m:
        largest = max(contours_m, key=cv2.contourArea)
        xm, ym, wm, hm = cv2.boundingRect(largest)
        refs["moha"] = (900 + xm + wm//2, 1380 + ym + hm//2)
    else:
        refs["moha"] = (1090, 1397)
    
    rep_region = arr[880:970, 500:1200]
    _, binary_r = cv2.threshold(rep_region, 100, 255, cv2.THRESH_BINARY_INV)
    contours_r, _ = cv2.findContours(binary_r, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours_r:
        largest = max(contours_r, key=cv2.contourArea)
        xr, yr, wr, hr = cv2.boundingRect(largest)
        refs["republic"] = (500 + xr + wr//2, 880 + yr + hr//2)
    else:
        refs["republic"] = (845, 932)
    
    print(f"[Fixed] Ref: PASSPORT={refs['passport']} MOHA={refs['moha']} Republic={refs['republic']}")
    
    # Character setup
    scale_f = min(W / 900, H / 630)
    sf = lambda v: max(1, int(v * scale_f))
    F = {
        "title": load_font("sans_bold", max(12, int(24*scale_f))),
        "val": load_font("sans_bold", max(9, int(13*scale_f))),
        "mrz": load_font("mono", max(10, int(18*scale_f))),
        "label": load_font("sans", max(8, int(11*scale_f))),
        "tiny": load_font("sans", max(6, int(8*scale_f))),
    }
    C = {"title": "#1a1a1a", "pass": "#6B0000", "val": "#111", "mrz": "#111", "label": "#666"}
    
    if not skip_header:
        draw_text_mask(draw, img, (refs["republic"][0], refs["republic"][1] - sf(15)),
                       "REPUBLIC OF THE UNION OF MYANMAR", F["title"], C["title"], "mm", debug, "title")
        draw_text_mask(draw, img, (refs["republic"][0], refs["republic"][1] + sf(21)),
                       "P A S S P O R T", F["title"], C["pass"], "mm", debug, "passport")
        draw_text_mask(draw, img, (W - sf(30), sf(25)), f"Passport No  {data['passport_no']}",
                       F["label"], "#1a1a1a", "rt", debug, "passport_no")
        draw_text_mask(draw, img, (W - sf(30), sf(45)),
                       f"Type  {data.get('type','PV')}    Code  {data.get('country_code','MMR')}",
                       F["tiny"], "#555", "rt", debug, "type_code")
    
    # Write fields using FIXED absolute positions for m03
    for key, pos in M03_FIELDS.items():
        val = str(data.get(key, ""))
        if not val:
            continue
        x, y = pos["x"], pos["y"]
        x = max(10, min(W - 100, x))
        y = max(10, min(H - 10, y))
        draw_text_mask(draw, img, (x, y), val, F["val"], C["val"], "lt", debug, f"val_{key}")
    
    # 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_x = 830
    mrz_y1 = 1125
    mrz_y2 = mrz_y1 + 65
    
    draw_text_mask(draw, img, (mrz_x, mrz_y1 - sf(6)), "P<", F["mrz"], C["mrz"], "lt", debug, "mrz_prefix")
    draw_text_mask(draw, img, (mrz_x + sf(20), mrz_y1), l1, F["mrz"], C["mrz"], "lt", debug, "mrz_l1")
    draw_text_mask(draw, img, (mrz_x + sf(20), mrz_y2), l2, F["mrz"], C["mrz"], "lt", debug, "mrz_l2")
    
    img.save(out_path, dpi=(300, 300))
    print(f"[Fixed] Output: {out_path} ({W}x{H})")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--img", required=True)
    parser.add_argument("--out", required=True)
    parser.add_argument("--data", required=True)
    parser.add_argument("--debug", action="store_true")
    parser.add_argument("--skip-header", action="store_true")
    args = parser.parse_args()
    
    if args.data.startswith("@"):
        with open(args.data[1:], "r", encoding="utf-8") as f:
            data = json.load(f)
    else:
        data = json.loads(args.data)
    
    write_passport_lazy_fixed(args.img, args.out, data, args.debug, args.skip_header)