"""M0 アセスメントキットの生成と採点。正本は maturity-model.yaml。

  ../.venv/bin/python kit.py build                          # questionnaire.md と scoring-sheet.xlsx を作る
  ../.venv/bin/python kit.py report sample/answers-sample.yaml  # 回答から診断レポート（Markdown + 図）を作る
"""

import datetime as dt
import pathlib
import sys

import yaml

HERE = pathlib.Path(__file__).resolve().parent
MODEL = yaml.safe_load((HERE / "maturity-model.yaml").read_text(encoding="utf-8"))
CONTROLS = MODEL["controls"]
LEVELS = MODEL["levels"]
TARGETS = MODEL["targets"]
RISK_ORDER = ["read_only", "write", "money_or_pii"]


# ---------------------------------------------------------------- build

def build_questionnaire() -> None:
    out = ["# M0 ヒアリング質問票（7統制）", "",
           "各質問を L1〜L5 で採点します。L3 が本番の最低ラインです。証跡で確認できない回答は、1段階下げて採点します。"
           "統制のレベルは、その統制で最も低い質問のレベルです。", "",
           "| レベル | 意味 |", "| --- | --- |"]
    out += [f"| L{k} | {v} |" for k, v in LEVELS.items()]
    out += ["", "## 接続候補のリスク区分と、本番で必要なレベル", "",
            "| 区分 | " + " | ".join(f"{c['num']} {c['name']}" for c in CONTROLS) + " |",
            "| --- |" + " --- |" * len(CONTROLS)]
    for key in RISK_ORDER:
        t = TARGETS[key]
        out.append(f"| {t['label']} | " + " | ".join(f"L{t['levels'][c['id']]}" for c in CONTROLS) + " |")
    for c in CONTROLS:
        out += ["", f"## 統制{c['num']} {c['name']}：{c['summary']}", "",
                "| ID | 質問 | L3 の基準 | L4 の基準 | 確認する証跡 |", "| --- | --- | --- | --- | --- |"]
        out += [f"| {q['id']} | {q['text']} | {q['l3']} | {q['l4']} | {q['evidence']} |" for q in c["questions"]]
    out += ["", "## 接続候補の棚卸し", "",
            "| 業務 | 使うシステム | 操作 | リスク区分 | 所管部署 |", "| --- | --- | --- | --- | --- |",
            "| 〔例：残高照会〕 | 〔勘定系 API〕 | 参照 | 参照のみ | 〔コールセンター〕 |", ""]
    (HERE / "questionnaire.md").write_text("\n".join(out), encoding="utf-8")


def build_sheet() -> None:
    from openpyxl import Workbook
    from openpyxl.formatting.rule import CellIsRule
    from openpyxl.styles import Alignment, Font, PatternFill
    from openpyxl.worksheet.datavalidation import DataValidation

    wb = Workbook()
    ws = wb.active
    ws.title = "採点"
    head = ["統制", "ID", "質問", "L3 の基準", "L4 の基準", "確認する証跡", "採点（1〜5）", "証跡・メモ"]
    ws.append(head)
    for cell in ws[1]:
        cell.font = Font(bold=True, color="FFFFFF")
        cell.fill = PatternFill("solid", fgColor="2A78D6")
        cell.alignment = Alignment(vertical="center", wrap_text=True)
    dv = DataValidation(type="whole", operator="between", formula1="1", formula2="5", allow_blank=True,
                        error="1〜5 の整数で入力してください", showErrorMessage=True)
    ws.add_data_validation(dv)
    ranges = {}
    for c in CONTROLS:
        start = ws.max_row + 1
        for q in c["questions"]:
            ws.append([f"{c['num']} {c['name']}", q["id"], q["text"], q["l3"], q["l4"], q["evidence"], None, None])
            dv.add(ws.cell(row=ws.max_row, column=7))
        ranges[c["id"]] = (start, ws.max_row)
    widths = [22, 7, 42, 42, 42, 30, 12, 36]
    for i, w in enumerate(widths, start=1):
        ws.column_dimensions[chr(64 + i)].width = w
    for row in ws.iter_rows(min_row=2):
        for cell in row:
            cell.alignment = Alignment(vertical="top", wrap_text=True)
    ws.freeze_panes = "C2"

    s = wb.create_sheet("サマリー")
    s.append(["統制", "現状（最低の質問）", "平均", "目標：参照のみ", "目標：更新あり", "目標：金銭・個人情報", "金銭・個人情報との差"])
    for cell in s[1]:
        cell.font = Font(bold=True)
    for c in CONTROLS:
        a, b = ranges[c["id"]]
        rng = f"採点!G{a}:G{b}"
        r = s.max_row + 1
        s.append([f"{c['num']} {c['name']}",
                  f'=IF(COUNT({rng})=0,"",MIN({rng}))',
                  f'=IF(COUNT({rng})=0,"",ROUND(AVERAGE({rng}),1))',
                  TARGETS["read_only"]["levels"][c["id"]],
                  TARGETS["write"]["levels"][c["id"]],
                  TARGETS["money_or_pii"]["levels"][c["id"]],
                  f'=IF(B{r}="","",B{r}-F{r})'])
    red = PatternFill("solid", fgColor="F8D4D4")
    s.conditional_formatting.add(f"G2:G{s.max_row}", CellIsRule(operator="lessThan", formula=["0"], fill=red))
    for col, w in zip("ABCDEFG", [24, 16, 8, 14, 14, 18, 18]):
        s.column_dimensions[col].width = w

    c2 = wb.create_sheet("接続候補")
    c2.append(["業務", "使うシステム", "操作（参照／更新）", "リスク区分", "所管部署", "優先度", "メモ"])
    for cell in c2[1]:
        cell.font = Font(bold=True)
    risk = DataValidation(type="list", formula1='"参照のみ,更新あり,金銭・個人情報が絡む"', allow_blank=True)
    c2.add_data_validation(risk)
    for r in range(2, 42):
        risk.add(c2.cell(row=r, column=4))
    for col, w in zip("ABCDEFG", [24, 22, 16, 22, 18, 8, 40]):
        c2.column_dimensions[col].width = w
    wb.save(HERE / "scoring-sheet.xlsx")


# ---------------------------------------------------------------- report

def control_levels(scores: dict) -> dict:
    result = {}
    for c in CONTROLS:
        vals = [scores[q["id"]] for q in c["questions"] if q["id"] in scores]
        result[c["id"]] = {"min": min(vals) if vals else None,
                           "avg": round(sum(vals) / len(vals), 1) if vals else None,
                           "weakest": [q["id"] for q in c["questions"] if scores.get(q["id"]) == min(vals)] if vals else []}
    return result


def draw_chart(levels: dict, target: dict, path: pathlib.Path, title: str) -> None:
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    from matplotlib import font_manager

    for name in ("Hiragino Sans", "YuGothic", "Hiragino Sans GB"):
        if any(f.name == name for f in font_manager.fontManager.ttflist):
            plt.rcParams["font.family"] = name
            break
    surface, ink, ink2, grid, series = "#fcfcfb", "#0b0b0b", "#52514e", "#e4e3df", "#2a78d6"
    labels = [f"{c['num']} {c['name']}" for c in CONTROLS][::-1]
    ids = [c["id"] for c in CONTROLS][::-1]
    fig, ax = plt.subplots(figsize=(8, 4.2), dpi=160)
    fig.patch.set_facecolor(surface)
    ax.set_facecolor(surface)
    for y, cid in enumerate(ids):
        cur, tgt = levels[cid]["min"], target[cid]
        ax.plot([min(cur, tgt), max(cur, tgt)], [y, y], color=grid, linewidth=6, solid_capstyle="round", zorder=1)
        ax.scatter([tgt], [y], marker="|", s=420, linewidths=2.5, color=ink2, zorder=2)
        ax.scatter([cur], [y], s=90, color=series, edgecolors=surface, linewidths=2, zorder=3)
        if cur < tgt:
            ax.text(5.35, y, f"−{tgt - cur}", va="center", ha="left", fontsize=9, color=ink)
    ax.set_yticks(range(len(ids)), labels, fontsize=9, color=ink)
    ax.set_xticks([1, 2, 3, 4, 5], [f"L{i}\n{LEVELS[i].split('（')[0]}" for i in range(1, 6)], fontsize=8, color=ink2)
    ax.set_xlim(0.7, 5.7)
    ax.axvline(3, color=grid, linewidth=1, zorder=0)
    for side in ("top", "right", "left"):
        ax.spines[side].set_visible(False)
    ax.spines["bottom"].set_color(grid)
    ax.tick_params(axis="y", length=0)
    ax.set_title(title, loc="left", fontsize=11, color=ink, pad=22)
    ax.scatter([], [], s=60, color=series, label="現状（最も低い質問）")
    ax.scatter([], [], marker="|", s=200, linewidths=2.5, color=ink2, label="本番に必要なレベル")
    ax.legend(loc="lower left", bbox_to_anchor=(0, 1.0), ncol=2, frameon=False, fontsize=8, labelcolor=ink2)
    ax.text(5.35, len(ids) - 0.35, "不足", fontsize=8, color=ink2, ha="left")
    fig.tight_layout()
    fig.savefig(path, facecolor=surface)
    plt.close(fig)


def report(answers_path: str) -> pathlib.Path:
    ans = yaml.safe_load(pathlib.Path(answers_path).read_text(encoding="utf-8"))
    levels = control_levels(ans["scores"])
    risks = [c["risk"] for c in ans.get("candidates", [])]
    top = max(risks, key=RISK_ORDER.index) if risks else "write"
    target = TARGETS[top]["levels"]
    gaps = [c for c in CONTROLS if levels[c["id"]]["min"] is not None and levels[c["id"]]["min"] < target[c["id"]]]
    below_l3 = [c for c in gaps if levels[c["id"]]["min"] < 3]

    out_dir = pathlib.Path(answers_path).resolve().parent / "report"
    out_dir.mkdir(exist_ok=True)
    chart = out_dir / "maturity-gap.png"
    draw_chart(levels, target, chart, f"{len(gaps)}つの統制が、本番に必要なレベルに届いていない")

    q_by_id = {q["id"]: q for c in CONTROLS for q in c["questions"]}
    lines = [
        f"# エージェント接続レディネス診断レポート：{ans['client']}",
        "",
        f"{ans.get('date', dt.date.today())} · 診断者 {ans.get('assessor', '')}",
        "",
        "## 1. 結論",
        "",
        (f"7つの統制のうち **{len(gaps)}つ** が、対象業務（最も高いリスク区分：{TARGETS[top]['label']}）の本番に必要な"
         f"レベルに届いていません。うち **{len(below_l3)}つ** は本番の最低ライン L3 を下回っており、"
         "更新系の業務を本番に出す前に解消が必要です。"),
        "",
        f"![成熟度とギャップ](report/{chart.name})",
        "",
        "## 2. 統制ごとの評価",
        "",
        "| 統制 | 現状 | 平均 | 必要 | 差 | 最も弱い質問 |",
        "| --- | --- | --- | --- | --- | --- |",
    ]
    for c in CONTROLS:
        lv = levels[c["id"]]
        diff = lv["min"] - target[c["id"]]
        weak = "、".join(f"{w} {q_by_id[w]['text']}" for w in lv["weakest"][:1])
        lines.append(f"| {c['num']} {c['name']} | L{lv['min']} | {lv['avg']} | L{target[c['id']]} | "
                     f"{'±0' if diff >= 0 else diff} | {weak} |")
    lines += ["", "## 3. 接続候補とリスク区分", "",
              "| 業務 | 使うシステム | 操作 | リスク区分 | 必要な水準を満たすか |", "| --- | --- | --- | --- | --- |"]
    for cand in ans.get("candidates", []):
        t = TARGETS[cand["risk"]]["levels"]
        ok = all(levels[c["id"]]["min"] >= t[c["id"]] for c in CONTROLS)
        lines.append(f"| {cand['name']} | {cand['system']} | {cand['operation']} | {TARGETS[cand['risk']]['label']} | "
                     f"{'満たす' if ok else '満たさない'} |")
    lines += ["", "## 4. ロードマップ", "", "### フェーズ1：本番の最低ライン（L3）に届かせる", ""]
    phase1 = [c for c in CONTROLS if levels[c["id"]]["min"] < 3]
    lines += [f"- [ ] **統制{c['num']} {c['name']}**：{c['actions']['to_l3']}" for c in phase1] or ["- 該当なし"]
    lines += ["", "### フェーズ2：金銭・個人情報を扱う業務に必要な水準（L4）に届かせる", ""]
    phase2 = [c for c in CONTROLS if levels[c["id"]]["min"] < 4 and TARGETS["money_or_pii"]["levels"][c["id"]] >= 4]
    lines += [f"- [ ] **統制{c['num']} {c['name']}**：{c['actions']['to_l4']}" for c in phase2] or ["- 該当なし"]
    lines += ["", "## 5. 推奨する次の一手", "", ans.get("next_step", "M1（参照アーキテクチャと PoC）で、フェーズ1の施策を1業務で実装し、本番化の判断材料を作ります。"), "",
              "## 付録：採点の根拠", "", "| ID | 採点 | 証跡・メモ |", "| --- | --- | --- |"]
    for qid, score in ans["scores"].items():
        lines.append(f"| {qid} | L{score} | {ans.get('evidence', {}).get(qid, '')} |")
    path = pathlib.Path(answers_path).resolve().parent / "report.md"
    path.write_text("\n".join(lines) + "\n", encoding="utf-8")
    return path


if __name__ == "__main__":
    cmd = sys.argv[1] if len(sys.argv) > 1 else "build"
    if cmd == "build":
        build_questionnaire()
        build_sheet()
        print("built questionnaire.md, scoring-sheet.xlsx")
    elif cmd == "report":
        print("report:", report(sys.argv[2]))
    else:
        sys.exit(__doc__)
