# -*- coding: utf-8 -*-
"""国内AI-OCR 6サービスの月額・初年度総額を、公式単価と架空の条件（月間枚数・1枚あたり項目数）から計算する。
使い方: python calculator.py input.json [expected.json]
  expected.json は配布した条件の検算用。自社の値に書き換えたときは付けずに実行する（付けると期待値と一致せず exit 1 になる）。
外部パッケージ不要（Python 3.10 以上）。ネットワーク接続なし。実帳票は送らない。
単価は input.json に書いた値（確認日つき）だけを使う。公式ページが変わったら input.json を書き換える。

前提（input.json の conditions）:
  - 定型帳票を項目抽出・定型読取で読む。非定型帳票のオプション費は含めない
  - 1枚あたりの読取項目数は items_per_page（項目課金のサービスで使う）
  - 日次ピークは pages / business_days（CAC の日次上限の判定に使う。実際の山がある場合は daily_peak_pages を直接書く）
  - 税は各社の表示のまま。AIよみと～るは公式が税込表示なので、税抜換算（÷1.1）も出す
  - DX Suite は LP の「全文読取 1枚あたり」の単価で計算する（料金ページは OCR をリクエスト課金とするが単価を載せていないため、LP の枚単価を使う）
  - AISpect プラン1000 の「1,000枚毎 10,000円」は 1,000枚単位の切り上げとして扱う（端数の扱いは公式ページに記載がない）
  - PaperOn は 1枚＝1読み取り回数として扱う（公式は「ファイル又はページ単位」）
"""
import json
import math
import sys
from pathlib import Path


def yen(v):
    return int(round(v))


def dxsuite(plan, pages, r):
    p = r["plans"][plan]
    usage = p["per_page_fulltext"] * pages
    over = max(0, usage - p["free_quota_per_month"])
    return {"plan": "DX Suite " + plan, "monthly": yen(p["monthly"] + over), "initial": p["initial"],
            "note": "全文読取 " + str(p["per_page_fulltext"]) + "円/枚・無料枠 " + str(p["free_quota_per_month"]) + "円分を超えた分が従量"}


def cac(pages, daily_peak, r):
    for p in r["plans"]:
        if pages <= p["pages_per_month"] and daily_peak <= p["pages_per_day"]:
            return {"plan": "CAC " + p["name"], "monthly": p["monthly"], "initial": 0,
                    "note": "月" + str(p["pages_per_month"]) + "枚・1日" + str(p["pages_per_day"]) + "枚まで。年契約の月々払い"}
    return {"plan": "CAC 上限超過（要問合せ）", "monthly": None, "initial": 0, "note": "Ultimate の上限を超える"}


def aispect(pages, r):
    p1 = r["plan1"]["base"] + r["plan1"]["per_page"] * pages
    p1000 = r["plan1000"]["base"] + r["plan1000"]["per_1000"] * math.ceil(pages / 1000)
    if p1 <= p1000:
        return {"plan": "AISpect プラン1", "monthly": yen(p1), "initial": r["initial"], "note": "基本料＋15円/枚"}
    return {"plan": "AISpect プラン1000", "monthly": yen(p1000), "initial": r["initial"], "note": "基本料＋1,000枚ごと10,000円（切り上げ）"}


def smartread(pages, items, r):
    annual_usage_by_plan = []
    for p in r["plans"]:
        usage = pages * items * p["per_item"] * 12
        annual_usage_by_plan.append((p, usage))
        if usage <= p["annual"]:
            return {"plan": "SmartRead " + p["name"], "monthly": yen(p["annual"] / 12), "initial": 0,
                    "note": "年額" + str(p["annual"]) + "円（無料枠と同額）。年間利用 " + str(yen(usage)) + "円分で枠内"}
    return {"plan": "SmartRead 個別見積もり", "monthly": None, "initial": 0, "note": "エンタープライズの枠を超える"}


def yomitoru(pages, items, r):
    best = None
    for p in r["plans"]:
        usage = pages * items * p["per_item_tax_in"]
        over = max(0, usage - p["quota_tax_in"])
        total = p["monthly_tax_in"] + over
        if best is None or total < best[1]:
            best = (p, total)
    p, total = best
    return {"plan": "AIよみと～る " + p["name"], "monthly_tax_in": yen(total), "monthly": yen(total / 1.1), "initial": 0,
            "note": "税込表示。読取金額 " + str(p["quota_tax_in"]) + "円/月まで込み、超過は " + str(p["per_item_tax_in"]) + "円/項目（税抜換算は÷1.1）"}


def paperon(pages, r):
    best = None
    for p in r["plans"]:
        short = max(0, pages - p["reads_per_month"])
        n = math.ceil(short / p["addon_reads"]) if short else 0
        if n > r["addon_max"]:
            continue
        total = p["monthly_annual_contract"] + n * r["addon_monthly"]
        if best is None or total < best[1] or (total == best[1] and n < best[2]):
            best = (p, total, n)
    if best is None:
        return {"plan": "PaperOn 上限超過（要問合せ）", "monthly": None, "initial": None, "note": ""}
    p, total, n = best
    return {"plan": "PaperOn " + p["name"] + ("＋追加" + str(n) + "口" if n else ""), "monthly": total, "initial": None,
            "note": "年額契約の月額。読み取り回数 " + str(p["reads_per_month"] + n * p["addon_reads"]) + "回/月。初期費用は公式ページに記載なし"}


def run(inp):
    rates = inp["rates"]
    results = []
    for c in inp["conditions"]:
        pages = c["pages"]
        items = c.get("items_per_page", inp["defaults"]["items_per_page"])
        daily = c.get("daily_peak_pages") or math.ceil(pages / inp["defaults"]["business_days"])
        rows = [
            dxsuite("Lite", pages, rates["dx_suite"]),
            dxsuite("Standard", pages, rates["dx_suite"]),
            cac(pages, daily, rates["cac"]),
            aispect(pages, rates["aispect"]),
            smartread(pages, items, rates["smartread"]),
            yomitoru(pages, items, rates["yomitoru"]),
            paperon(pages, rates["paperon"]),
        ]
        for row in rows:
            m = row["monthly"]
            ini = row["initial"] or 0
            row["first_year"] = yen(m * 12 + ini) if m is not None else None
        results.append({"condition": c["name"], "pages": pages, "items_per_page": items, "daily_peak_pages": daily, "rows": rows})
    return results


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(2)
    inp = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
    results = run(inp)
    for r in results:
        print("== " + r["condition"] + "  月" + str(r["pages"]) + "枚・1枚" + str(r["items_per_page"]) + "項目・日次ピーク" + str(r["daily_peak_pages"]) + "枚")
        for row in r["rows"]:
            m = "-" if row["monthly"] is None else format(row["monthly"], ",") + "円"
            fy = "-" if row["first_year"] is None else format(row["first_year"], ",") + "円"
            extra = ("（税込 " + format(row["monthly_tax_in"], ",") + "円）") if "monthly_tax_in" in row else ""
            print("  " + row["plan"].ljust(26) + " 月額 " + m.rjust(10) + extra + "  初年度 " + fy.rjust(12) + "  " + row["note"])
    if len(sys.argv) >= 3:
        exp = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
        got = {r["condition"] + "|" + row["plan"]: [row["monthly"], row["first_year"]] for r in results for row in r["rows"]}
        ng = 0
        for k, v in exp.items():
            if got.get(k) != v:
                print("NG", k, "expected", v, "got", got.get(k))
                ng += 1
        missing = [k for k in got if k not in exp]
        if missing:
            print("NG expected.json に無い行:", missing)
            ng += len(missing)
        print("期待値との照合:", "OK" if ng == 0 else "NG " + str(ng) + "件")
        sys.exit(1 if ng else 0)


if __name__ == "__main__":
    main()
