"""実験の素材。編集課題（E）と調べもの課題（R）。すべて架空のデータ。"""
import random
import re

# ---------------------------------------------------------------- 編集課題
EDIT_DOC = """# Notebox 運用ガイド

## 第1章 はじめに

Notebox は、チームでノートを共有するサービスです。この章では、ワークスペースの考え方を説明します。ワークスペースは、メンバーとノートをまとめる入れ物です。新しいワークスペースを作ると、作成者が管理者になります。

料金は第2章、データの扱いは第3章、障害時の連絡は第5章にあります。

## 第2章 料金

Notebox には、スタンダードとビジネスの2つのプランがあります。どちらも、料金はワークスペース単位です。

| プラン | 月額（ワークスペース1つ） | 年払いの年額 | メンバー上限 |
|---|---|---|---|
| スタンダード | 4,800円 | 57,600円 | 10人 |
| ビジネス | 9,800円 | 117,600円 | 50人 |

スタンダードプランを年払いにしても、割引はありません。年額は月額を12か月分払った額（4,800円×12か月＝57,600円）です。ワークスペースを5つ契約するときは、スタンダードなら月額24,000円になります。

解約は、更新日の30日前までに申し込みます。

## 第3章 データの保管

ワークスペース内のノートは、削除してから30日間、ごみ箱に残ります。ごみ箱から復元できるのは、ワークスペースの管理者だけです。

操作の記録（監査ログ）は30日間保管します。保管期間を過ぎたデータは完全に削除され、復元できません。バックアップからの復元手順は、第4章を参照してください。

## 第4章 バックアップと復元

バックアップは毎日午前3時に自動で取られ、ワークスペースごとに保存されます。復元を依頼するときは、第5章の窓口へ連絡します。

ノートの保管期間（第3章）を過ぎたデータは、バックアップにも残りません。復元の対象になるのは、保管期間内のワークスペースだけです。

## 第5章 障害時の連絡

障害が起きたら、ワークスペースの管理者が窓口（support@notebox.example.invalid）へ連絡します。窓口の受付は、平日の午前9時から午後6時までです。

料金プランによる対応の違いは、第2章を参照してください。障害の影響を受けるワークスペースの名前を、連絡に必ず書いてください。

## 第6章 よくある質問

Q. ワークスペースは何個まで作れますか？
A. スタンダードプランでは3個までです。3個作ると、月額は14,400円（4,800円×3）になります。

Q. ワークスペースを削除するとどうなりますか？
A. 削除したワークスペースのノートは、第3章のとおりごみ箱に残ります。

Q. 解約したらデータはどうなりますか？
A. 解約から30日後に、すべてのデータを削除します。
"""

EDIT_EXTRA_L = """

## 第7章 セキュリティ

Notebox では、ワークスペースへのログインに二段階認証を使えます。管理者は、ワークスペース全体で二段階認証を必須にできます。

監査ログ（第3章）は、30日間のうちなら、管理画面から検索できます。不審な操作に気づいたら、第5章の窓口へ連絡してください。

## 第8章 移行ガイド

ほかのサービスから Notebox へ移るときの目安です。スタンダードプランでメンバーが10人いる場合、1人あたりの月額は480円（4,800円÷10人）です。

移行前のサービスの料金が1人あたり月額600円なら、1人あたり1か月で120円安くなります。移行の相談は、第5章の窓口で受け付けます。

## 第9章 用語集

- ワークスペース：メンバーとノートをまとめる入れ物。料金の単位でもあります（第2章）。
- ごみ箱：削除したノートが30日間残る場所です（第3章）。
- 監査ログ：操作の記録です。30日間保管します（第3章）。
- 管理者：ワークスペースの設定を変えられる人です。

## 第10章 お問い合わせ先

料金や契約のご相談は、営業窓口（sales@notebox.example.invalid）へどうぞ。受付は、平日の午前9時から午後6時までです。

障害の連絡は、第5章の窓口へお願いします。
"""
EDIT_DOC_L = EDIT_DOC.rstrip("\n") + "\n" + EDIT_EXTRA_L


NEW_SECTION = """## 第4章 権限と共有

プロジェクトの管理者は、メンバーごとに「閲覧のみ」か「編集できる」かを決められます。ノートを外部に共有するときは、管理者の許可が必要です。
"""

EDIT_REQUEST = """次の6つの変更を、文書に反映してください。

1. 用語の統一：「ワークスペース」を「プロジェクト」に言い換える。見出しと本文のすべてが対象。
2. 料金の改定：スタンダードプランの月額を4,800円から5,200円にする。この料金から計算している数字（年額、合計など）も、すべて計算し直して更新する。ビジネスプランは変えない。
3. 保管期間の延長：ごみ箱の保管を30日間から60日間に、監査ログの保管を30日間から90日間にする。解約に関する30日（申込期限と削除までの日数）は変えない。
4. 章の追加：第3章の直後に、次の新しい章を挿入する。以降の章は番号を1つずつ繰り下げる。本文中の「第N章」という参照も、繰り下げ後の番号に合わせる。

{new_section}
5. 受付時間の変更：障害時の窓口（第5章）の受付を「午前9時から午後6時」から「午前9時から午後8時」にする。ほかの窓口の受付時間は変えない。
6. 上の5つに関係しない部分は、一字も変えない。
"""


def edit_request():
    return EDIT_REQUEST.format(new_section=NEW_SECTION.strip())


def edit_doc(size):
    return EDIT_DOC_L if size == "L" else EDIT_DOC


def split_edit_doc(size):
    """文書を3つの担当に分ける。S: 第1〜2 / 第3〜4 / 第5〜6章。L: 第1〜3 / 第4〜7 / 第8〜10章。タイトル行は担当1。"""
    parts = edit_doc(size).split("\n## ")
    head = parts[0]
    secs = ["## " + p for p in parts[1:]]
    cuts = [0, 3, 7, 10] if size == "L" else [0, 2, 4, 6]
    groups = ["\n".join(secs[cuts[i]:cuts[i + 1]]) for i in range(3)]
    groups[0] = head + "\n" + groups[0]
    return [g.rstrip() + "\n" for g in groups]


def part_names(size):
    if size == "L":
        return ["第1〜3章（と文書の表題）", "第4〜7章", "第8〜10章"]
    return ["第1〜2章（と文書の表題）", "第3〜4章", "第5〜6章"]


def expected_edit(size="S"):
    """Apply only the six requested changes to the original document."""
    text = edit_doc(size).replace("ワークスペース", "プロジェクト")
    for old, new in [("4,800", "5,200"), ("57,600", "62,400"),
                     ("24,000", "26,000"), ("14,400", "15,600"),
                     ("480円", "520円"), ("120円", "80円"),
                     ("30日間、ごみ箱", "60日間、ごみ箱"),
                     ("監査ログ）は30日間保管", "監査ログ）は90日間保管"),
                     ("30日間のうちなら", "90日間のうちなら"),
                     ("ノートが30日間残る", "ノートが60日間残る"),
                     ("操作の記録です。30日間保管", "操作の記録です。90日間保管"),
                     ("窓口の受付は、平日の午前9時から午後6時", "窓口の受付は、平日の午前9時から午後8時")]:
        text = text.replace(old, new)
    # Renumber the old chapters and references before inserting the new chapter.
    text = re.sub(r"第(\d+)章", lambda m: f"第{int(m[1]) + (int(m[1]) >= 4)}章", text)
    return text.replace("## 第5章 バックアップと復元", NEW_SECTION + "\n## 第5章 バックアップと復元", 1)


def nonempty_lines(text):
    # Ignore blank-line and line-ending differences only; preserve other whitespace.
    return [line for line in text.splitlines() if line.strip()]


def row_checks(row):
    """Regrade saved editing finals; preserve historical research checks."""
    return grade_edit(row["final"], "L") if row["cond"].startswith("E") else row["checks"]


def grade_edit(text: str, size="S"):
    """各チェックは（名前, 合否）。完成文書全体に対して判定する。"""
    t = text
    n_ws = edit_doc(size).count("ワークスペース")
    checks = {}
    checks["term_no_old"] = "ワークスペース" not in t
    checks["term_count"] = t.count("プロジェクト") >= n_ws
    checks["price_new"] = t.count("5,200円") >= 2 and "4,800" not in t
    checks["year_total"] = "62,400円" in t and "5,200円×12か月＝62,400円" in t and "57,600" not in t
    checks["five_projects"] = "月額26,000円" in t and "24,000" not in t
    checks["faq_total"] = "15,600円（5,200円×3）" in t and "14,400" not in t
    checks["business_kept"] = "9,800円" in t and "117,600円" in t
    checks["trash_60"] = "60日間、ごみ箱" in t
    checks["audit_90"] = "監査ログ）は90日間保管" in t
    checks["cancel_notice_kept"] = "更新日の30日前までに申し込みます" in t
    checks["cancel_delete_kept"] = "解約から30日後に" in t
    checks["new_section"] = "## 第4章 権限と共有" in t and "管理者の許可が必要です" in t
    checks["headings"] = all(h in t for h in [
        "## 第5章 バックアップと復元", "## 第6章 障害時の連絡", "## 第7章 よくある質問",
    ]) and "## 第4章 バックアップ" not in t and "## 第5章 障害" not in t and "## 第6章 よくある" not in t
    checks["ref_ch1"] = "障害時の連絡は第6章にあります" in t and "データの扱いは第3章" in t and "料金は第2章" in t
    checks["ref_ch3"] = "復元手順は、第5章を参照" in t
    checks["ref_ch4"] = "第6章の窓口へ連絡" in t and "保管期間（第3章）" in t
    checks["ref_ch5"] = "対応の違いは、第2章を参照" in t
    checks["ref_ch6"] = "第3章のとおりごみ箱" in t
    if size == "L":
        checks["hours"] = "午後8時までです" in t and t.count("午後6時") == 1 and "営業窓口（sales@notebox.example.invalid）へどうぞ。受付は、平日の午前9時から午後6時までです" in t
    else:
        checks["hours"] = "午後8時までです" in t and "午後6時" not in t
    checks["untouched"] = all(s in t for s in [
        "support@notebox.example.invalid", "毎日午前3時に自動で取られ", "メンバー上限", "50人",
        "復元の対象になるのは、保管期間内のプロジェクトだけです", "作成者が管理者になります",
    ])
    if size == "L":
        checks["l_headings"] = all(h in t for h in [
            "## 第8章 セキュリティ", "## 第9章 移行ガイド", "## 第10章 用語集", "## 第11章 お問い合わせ先"])
        checks["l_audit_in_security"] = "90日間のうちなら" in t
        checks["l_ref_security"] = "第6章の窓口へ連絡してください" in t
        checks["l_migrate_calc"] = "520円（5,200円÷10人）" in t and "80円安くなります" in t and "480円" not in t and "120円" not in t
        checks["l_migrate_ref"] = "第6章の窓口で受け付けます" in t
        checks["l_glossary"] = ("プロジェクト：メンバーとノートをまとめる入れ物。料金の単位でもあります（第2章）" in t
                                and "ごみ箱：削除したノートが60日間残る" in t and "操作の記録です。90日間保管します（第3章）" in t)
        checks["l_contact_ref"] = "障害の連絡は、第6章の窓口へお願いします" in t
        checks["l_untouched"] = "移行前のサービスの料金が1人あたり月額600円" in t and "二段階認証" in t
    checks["expected_document"] = nonempty_lines(t) == nonempty_lines(expected_edit(size))
    return checks


# ---------------------------------------------------------------- 調べもの課題
_BASE = [
    # name, 月額(現行), 旧月額, 年払い割引率, 最大ファイルMB, 動画MB, region(現行), 旧region, 無料席数
    ("Aster", 4200, 3600, 15, 80, 500, "東京", "大阪", 3),
    ("Birch", 3500, 3900, 10, 120, 800, "東京", "シンガポール", 5),
    ("Cedar", 5800, 5000, 20, 60, 300, "フランクフルト", "東京", 2),
    ("Drift", 3000, 2800, 25, 200, 1000, "大阪", "東京", 1),
    ("Ember", 4800, 4800, 5, 150, 600, "東京", "バージニア", 4),
    ("Flint", 2900, 3300, 0, 100, 400, "シンガポール", "東京", 6),
]
_NAMES = ["Grove", "Haze", "Iris", "Juno", "Kite", "Lumen", "Maple", "Nova", "Onyx", "Pine", "Quill", "Reed", "Sage", "Tide"]
_REGIONS = ["東京", "大阪", "シンガポール", "フランクフルト", "バージニア"]


def _more_products():
    rnd = random.Random(2026)
    out = []
    used_monthly = set(p[1] * 12 * (100 - p[3]) // 100 for p in _BASE)
    used_mb = set(p[4] for p in _BASE)
    for n in _NAMES:
        while True:
            monthly = rnd.randrange(2500, 6500, 100)
            disc = rnd.choice([0, 5, 10, 15, 20, 25])
            yearly = monthly * 12 * (100 - disc) // 100
            mb = rnd.randrange(40, 260, 10)
            if yearly not in used_monthly and mb not in used_mb and yearly > 27000:  # 最安・最大は固定の製品に任せる
                break
        used_monthly.add(yearly); used_mb.add(mb)
        old = monthly + rnd.choice([-500, -300, 300, 500, 700])
        reg, oreg = rnd.sample(_REGIONS, 2)
        out.append((n, monthly, old, disc, mb, rnd.randrange(200, 1200, 100), reg, oreg, rnd.randint(1, 8)))
    return out


def products(size):
    return _BASE if size == "S" else _BASE + _more_products()


FILLER = [
    "管理画面からは、利用状況をグラフで確認できます。",
    "メンバーの招待は、メールアドレスを入力するだけで完了します。",
    "通知の設定は、個人ごとに変えられます。",
    "ダークモードは、画面右上のメニューから切り替えられます。",
    "ノートには、タグを最大20個まで付けられます。",
    "検索では、タイトルと本文の両方が対象になります。",
    "スマートフォンのアプリは、iOSとAndroidの両方で使えます。",
    "共有リンクには、有効期限を付けられます。",
    "コメントには、メンションを付けて相手に通知できます。",
    "テンプレートは、チームで共有して使い回せます。",
    "変更履歴は、直近の50件まで画面で確認できます。",
    "ショートカットキーの一覧は、ヘルプメニューにあります。",
    "ログインには、パスワードのほかに二段階認証も使えます。",
    "外部サービスとの連携は、設定画面の「連携」から追加します。",
    "CSVでの書き出しは、一覧画面のメニューから実行します。",
    "問い合わせの返信は、通常、2営業日以内に届きます。",
    "週に一度、利用状況のまとめがメールで届きます。",
    "ファイル名に使えない文字は、自動で置き換えられます。",
    "印刷用のレイアウトも、あらかじめ用意されています。",
    "大きな画像は、アップロード時に自動で縮小できます。",
]


def _page(p, seed, k=1):
    name, monthly, old_monthly, disc, fmb, vmb, region, old_region, seats = p
    rnd = random.Random(seed)
    def filler(n):
        return "".join(rnd.sample(FILLER, min(len(FILLER), n * k)))
    annual_text = (f"年払いを選ぶと、月額料金から{disc}%引きになります。" if disc
                   else "年払いの割引はありません。年払いでも、月額料金の12か月分を払います。")
    lines = [
        f"# {name} 製品ガイド（2026年10月版）", "",
        "## 概要", "", f"{name} は、チームのノートと資料を管理するサービスです。" + filler(4), "",
        "## 料金", "",
        f"現行の月額料金は、1席あたり{monthly:,}円です。2026年4月の改定前は、月額{old_monthly:,}円でした。" + annual_text +
        "料金は、すべて税抜です。" + filler(3), "",
        "## ファイルの上限", "",
        f"1ファイルあたりの最大サイズは{fmb}MBです。ただし、動画ファイルは別枠で、1本あたり{vmb}MBまで扱えます。" + filler(4), "",
        "## データの保管場所", "",
        f"データは、{region}リージョンに保管されます。{old_region}リージョンは、2026年3月で提供を終了しました。" + filler(3), "",
        "## 無料プラン", "",
        f"無料プランでは、{seats}席まで追加料金なしで使えます。" + filler(4), "",
    ]
    return "\n".join(lines)


def source_pages(size):
    return {p[0]: _page(p, seed=i, k=1 if size == "S" else 3) for i, p in enumerate(products(size))}


def research_gold(size):
    gold = {}
    for name, monthly, old, disc, fmb, vmb, region, old_region, seats in products(size):
        yearly = round(monthly * 12 * (100 - disc) / 100)
        gold[name] = {"yearly_yen": yearly, "max_file_mb": fmb, "region": region, "free_seats": seats}
    cheapest = min(gold, key=lambda k: gold[k]["yearly_yen"])
    assert sorted(v["yearly_yen"] for v in gold.values())[0] != sorted(v["yearly_yen"] for v in gold.values())[1]
    tokyo = [k for k in gold if gold[k]["region"] == "東京"]
    biggest_tokyo = max(tokyo, key=lambda k: gold[k]["max_file_mb"])
    assert sorted(gold[k]["max_file_mb"] for k in tokyo)[-1] != sorted(gold[k]["max_file_mb"] for k in tokyo)[-2]
    gold["_q1"] = cheapest
    gold["_q2"] = biggest_tokyo
    return gold


FIELDS_DESC = """各製品について、次の4項目を答えてください。
- yearly_yen: 年払いを選んだときの1席あたりの年間料金（円、整数）。現行の月額料金を使い、割引があれば引く。
- max_file_mb: 動画以外のファイルの最大サイズ（MB、整数）。
- region: 現在データを保管しているリージョン名（「リージョン」の字は付けない）。
- free_seats: 無料プランで追加料金なしで使える席数（整数）。"""

CROSS_Q = """さらに、次の2つに製品名で答えてください。
- q1: yearly_yen が最も小さい製品
- q2: region が「東京」の製品のうち、max_file_mb が最も大きい製品"""
