"""架空の請求書10種を描き、3段階(L0きれい/L1ふつうのスキャン/L2悪いスキャン)の画像PDFにする。"""
import json, random
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance
import numpy as np

SANS = "/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc"
MARU = "/System/Library/Fonts/ヒラギノ丸ゴ ProN W4.ttc"
def F(sz, p=SANS): return ImageFont.truetype(p, sz)
W, H = 1240, 1754
yen = lambda n: ("△" if n < 0 else "") + f"{abs(n):,}"

DOCS = [
 dict(id="d01", no="INV-2026-0317", date=(2026,10,7), era=False, vendor="株式会社 青葉商事", rate="10",
      lines=[("コピー用紙 A4 500枚",12,480),("トナーカートリッジ",3,8700),("ファイルボックス",20,260)]),
 dict(id="d02", no="2026-10-0781", date=(2026,10,12), era=False, vendor="有限会社 ミナト工業", rate="10",
      lines=[("六角ボルト M8",100,17),("ワッシャー M8",100,6),("ステンレス板 t1.5",4,3180),("加工費",1,16800),("送料",1,1750)]),
 dict(id="d03", no="K-61038", date=(2026,9,30), era=False, vendor="株式会社 ひのき食品", rate="mixed",
      lines=[("ミネラルウォーター 2L（※）",48,98),("コーヒー豆 1kg",6,2680),("紙コップ 100個",10,420),("緑茶ティーバッグ（※）",12,540)]),
 dict(id="d04", no="INV-0067-18", date=(2026,10,1), era=False, vendor="合同会社 グリーンリーフ", rate="10",
      lines=[("保守サービス 10月分",1,38000),("追加ライセンス",5,1900),("初回割引",1,-5000)]),
 dict(id="d05", no="S20261003", date=(2026,10,3), era=False, vendor="株式会社 カワセ設備", rate="10",
      lines=[("給湯器点検",2,6500),("部品交換（バルブ）",3,3870)], carry=186300),
 dict(id="d06", no="NT-20260918-06", date=(2026,9,18), era=False, vendor="北斗印刷 株式会社", rate="10",
      lines=[("パンフレット A4 4P",500,86),("名刺 100枚",5,1250),("折り加工",1,3800)], strike=1),
 dict(id="d07", no="R-20261007-0618", date=(2026,10,7), era=True, vendor="株式会社 みどり薬品", rate="10",
      lines=[("衛生用品セット",30,1380),("消毒液 500ml",24,690),("マスク 50枚入",16,520)]),
 dict(id="d08", no="B-80195", date=(2026,10,9), era=False, vendor="セイワ電機 株式会社", rate="10",
      lines=[("LED電球 E26",24,780),("延長コード 3m",10,1190),("電池 単3 20本",8,1480),("ケーブルタイ",30,160),
             ("結束バンド",20,210),("テープ 18mm",12,330),("ドライバー",6,980),("ペンチ",4,1650),("軍手",40,95),("収納ケース",5,2480)]),
 dict(id="d09", no="9031-2026", date=(2026,10,15), era=False, vendor="株式会社 トリトン物流", rate="10",
      lines=[("配送料 10/1〜10/15",1,52900),("保管料",1,18700),("梱包資材",1,6030)]),
 dict(id="d10", no="INV-1006", date=(2026,10,6), era=False, vendor="スタジオ 風車", rate="inc",
      lines=[("撮影料（税込）",1,77000),("レタッチ（税込）",6,2200),("データ納品（税込）",1,5500)]),
]

def calc(d):
    ls = d["lines"]
    if d["rate"] == "10":
        net = sum(q*p for _,q,p in ls); tax = net//10; gross = net+tax
        tax_rows = [("10%対象", net, tax)]
    elif d["rate"] == "mixed":
        n8 = sum(q*p for n,q,p in ls if "※" in n); n10 = sum(q*p for n,q,p in ls if "※" not in n)
        t8 = n8*8//100; t10 = n10//10; net = n8+n10; tax = t8+t10; gross = net+tax
        tax_rows = [("8%対象（※軽減）", n8, t8), ("10%対象", n10, t10)]
    else:
        gross = sum(q*p for _,q,p in ls); tax = gross*10//110; net = gross-tax
        tax_rows = [("10%対象（内税）", gross, tax)]
    return net, tax, gross, tax_rows

def render(d):
    net, tax, gross, tax_rows = calc(d)
    im = Image.new("RGB", (W, H), "white"); g = ImageDraw.Draw(im)
    y, m = d["date"][0], d["date"][1]; dd = d["date"][2]
    dtxt = f"令和{y-2018}年{m}月{dd}日" if d["era"] else f"{y}年{m}月{dd}日"
    g.text((W//2, 90), "請 求 書", font=F(56), fill="black", anchor="mm")
    g.text((90, 190), f"{d['vendor']} 御中" if False else "株式会社アルテコ 御中", font=F(34), fill="black")
    g.line((90, 235, 560, 235), fill="black", width=2)
    g.text((W-90, 170), f"請求番号　{d['no']}", font=F(28), fill="black", anchor="ra")
    g.text((W-90, 210), f"請求日　{dtxt}", font=F(28), fill="black", anchor="ra")
    g.text((W-90, 290), d["vendor"], font=F(32), fill="black", anchor="ra")
    g.text((W-90, 332), "東京都港区芝浦 1-2-3　TEL 03-0000-0000", font=F(20), fill="black", anchor="ra")
    g.text((90, 300), "下記のとおり御請求申し上げます。", font=F(24), fill="black")
    g.rectangle((90, 380, 700, 450), outline="black", width=3)
    g.text((110, 415), "御請求金額", font=F(26), fill="black", anchor="lm")
    g.text((680, 415), f"¥{gross:,}-", font=F(40), fill="black", anchor="rm")
    rowh = 52 if len(d["lines"]) <= 6 else 44
    top = 500
    cols = [90, 700, 800, 960, W-90]
    for i, t in enumerate(["品名","数量","単価","金額"]):
        g.text(((cols[i]+cols[i+1])/2, top+22), t, font=F(24), fill="black", anchor="mm")
    g.line((90, top, W-90, top), fill="black", width=2); g.line((90, top+44, W-90, top+44), fill="black", width=2)
    yy = top+44
    for i,(n,q,p) in enumerate(d["lines"]):
        amt = q*p; yy += rowh
        g.text((100, yy-rowh/2), n, font=F(24), fill="black", anchor="lm")
        g.text((cols[2]-10, yy-rowh/2), f"{q:,}", font=F(24), fill="black", anchor="rm")
        g.text((cols[3]-10, yy-rowh/2), yen(p), font=F(24), fill="black", anchor="rm")
        g.text((cols[4]-10, yy-rowh/2), yen(amt), font=F(24), fill="black", anchor="rm")
        g.line((90, yy, W-90, yy), fill="#888", width=1)
    g.line((90, top, 90, yy), fill="black", width=2); g.line((W-90, top, W-90, yy), fill="black", width=2)
    for c in cols[1:4]: g.line((c, top, c, yy), fill="#888", width=1)
    yy += 40
    x = 640
    g.text((x, yy), "小計（税抜）", font=F(24), fill="black"); g.text((W-100, yy), yen(net), font=F(26), fill="black", anchor="ra"); yy += 44
    for lab, base, t in tax_rows:
        g.text((x, yy), f"{lab} 消費税", font=F(22), fill="black"); g.text((W-100, yy), yen(t), font=F(24), fill="black", anchor="ra"); yy += 40
    if d["rate"] == "inc":
        g.text((x, yy), "（上記は内税）", font=F(20), fill="black"); yy += 36
    g.line((x, yy, W-90, yy), fill="black", width=2); yy += 14
    g.text((x, yy), "合計（税込）", font=F(28), fill="black")
    if d.get("strike"):
        old = gross + 2800
        g.text((W-100, yy), yen(old), font=F(30), fill="black", anchor="ra")
        g.line((W-100-150, yy+16, W-100, yy+16), fill="black", width=4)
        g.text((W-100, yy+44), yen(gross), font=F(30, MARU), fill="#1d3fbf", anchor="ra")
        yy += 50
    else:
        g.text((W-100, yy), yen(gross), font=F(30), fill="black", anchor="ra")
    yy += 70
    if d.get("carry"):
        g.text((90, yy), f"前回御請求残高　¥{d['carry']:,}（入金確認中）", font=F(22), fill="black"); yy += 36
    g.text((90, yy+20), "お振込先　○○銀行 芝浦支店　普通 1234567　カ)アルテコ", font=F(22), fill="black")
    g.text((90, yy+60), "お支払期限　翌月末日", font=F(22), fill="black")
    return im, dict(invoice_no=d["no"], invoice_date=f"{y:04d}-{m:02d}-{dd:02d}", net=net, tax=tax, gross=gross)

def degrade(im, level, seed):
    rng = np.random.default_rng(seed)
    if level == 0: return im
    sc, blur, noise, rot, q = {1:(0.55,0.8,6,1.2,45), 2:(0.34,1.1,16,2.6,28)}[level]
    im = im.rotate(rng.uniform(-rot, rot), expand=False, fillcolor="white", resample=Image.BICUBIC)
    w,h = im.size; small = im.resize((int(w*sc), int(h*sc)), Image.BILINEAR).filter(ImageFilter.GaussianBlur(blur))
    a = np.asarray(small).astype(np.float32)
    a += rng.normal(0, noise, a.shape)
    yy = np.linspace(1.0, 0.82 if level==2 else 0.92, a.shape[0])[:,None,None]; a *= yy
    if level == 2: a = 128 + (a-128)*0.8
    small = Image.fromarray(np.clip(a,0,255).astype(np.uint8))
    import io
    buf = io.BytesIO(); small.save(buf, "JPEG", quality=q); buf.seek(0)
    return Image.open(buf).convert("RGB")

if __name__ == "__main__":
    import os
    os.makedirs("docs", exist_ok=True)
    truth = {}
    for i, d in enumerate(DOCS):
        im, t = render(d); truth[d["id"]] = t
        for lv in (0,1,2):
            out = degrade(im, lv, seed=100+i*3+lv)
            out.save(f"docs/{d['id']}_L{lv}.pdf", "PDF", resolution=150*{0:1,1:0.55,2:0.34}[lv])
            out.save(f"docs/{d['id']}_L{lv}.png")
    json.dump(truth, open("truth.json","w"), ensure_ascii=False, indent=1)
    print(json.dumps(truth, ensure_ascii=False, indent=0)[:900])
