from __future__ import annotations

import tkinter as tk
from collections.abc import Callable
from datetime import date
from decimal import Decimal
from pathlib import Path
from tkinter import messagebox, ttk

from ccma.domain.contributions import (
    CLAIM_STATUS_LABELS,
    allocated_total,
    claim_balance,
    claim_items,
    claim_status,
    claim_total,
    decimal_value,
    money_text,
    payment_allocated_total,
)
from ccma.domain.dates import date_input_hint, format_date_for_display
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.document_dialog import DocumentTemplateDialog
from ccma.ui.labels import (
    CLAIM_ITEM_TYPE_LABELS,
    REMINDER_CHANNEL_LABELS,
    REMINDER_STATUS_LABELS,
    display_label,
    storage_key,
)


class ClaimTab(ttk.Frame):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        member_id: str,
        claim_id: str,
        on_close: Callable[[], None],
        on_changed: Callable[[], None],
    ):
        super().__init__(master, padding=12)
        self.repository = repository
        self.member_id = member_id
        self.claim_id = claim_id
        self.on_close = on_close
        self.on_changed = on_changed
        self._build_ui()
        self.refresh()

    def _build_ui(self) -> None:
        self.columnconfigure(0, weight=1)
        self.rowconfigure(2, weight=1)
        header = ttk.Frame(self)
        header.grid(row=0, column=0, sticky="ew", pady=(0, 12))
        header.columnconfigure(0, weight=1)
        self.title_var = tk.StringVar()
        self.subtitle_var = tk.StringVar()
        ttk.Label(header, textvariable=self.title_var, style="TabTitle.TLabel").grid(
            row=0, column=0, sticky="w"
        )
        ttk.Label(header, textvariable=self.subtitle_var, style="Mono.TLabel").grid(
            row=1, column=0, sticky="w", pady=(3, 0)
        )
        ttk.Button(header, text="Tab schließen", command=self.on_close).grid(row=0, column=1, rowspan=2)

        summary = ttk.Frame(self)
        summary.grid(row=1, column=0, sticky="ew", pady=(0, 12))
        self.summary_vars: dict[str, tk.StringVar] = {}
        for column, (key, title) in enumerate(
            (("total", "GESAMT"), ("paid", "BEZAHLT"), ("balance", "OFFEN"), ("status", "STATUS"))
        ):
            card = ttk.Frame(summary, style="Card.TFrame", padding=12)
            card.grid(row=0, column=column, sticky="nsew", padx=(0, 8))
            summary.columnconfigure(column, weight=1)
            ttk.Label(card, text=title, style="CardTitle.TLabel").pack(anchor="w")
            variable = tk.StringVar()
            self.summary_vars[key] = variable
            ttk.Label(card, textvariable=variable, style="CardValue.TLabel").pack(anchor="w", pady=(5, 0))

        self._build_ledger()

        footer = ttk.Frame(self)
        footer.grid(row=3, column=0, sticky="ew", pady=(10, 0))
        footer.columnconfigure(0, weight=1)
        self.hold_button = ttk.Button(footer, text="Mahnsperre setzen", command=self._toggle_hold)
        self.hold_button.grid(row=0, column=0, sticky="w")
        self.cancel_button = ttk.Button(footer, text="Forderung stornieren", command=self._cancel_claim)
        self.cancel_button.grid(row=0, column=1, sticky="e")

    def _build_ledger(self) -> None:
        ledger = ttk.Frame(self, padding=12)
        ledger.grid(row=2, column=0, sticky="nsew")
        ledger.columnconfigure(0, weight=1)
        ledger.rowconfigure(0, weight=1)
        self.ledger = ttk.Treeview(
            ledger,
            columns=("date", "description", "quantity", "unit", "amount", "status", "reference"),
            show="tree headings",
        )
        self.ledger.heading("#0", text="Bereich / Element")
        self.ledger.column("#0", width=190, minwidth=140, anchor="w")
        for key, title, width in (
            ("date", "Datum", 120),
            ("description", "Beschreibung", 300),
            ("quantity", "Menge", 75),
            ("unit", "Einzelpreis", 100),
            ("amount", "Betrag", 105),
            ("status", "Status / Details", 180),
            ("reference", "Referenz", 210),
        ):
            self.ledger.heading(key, text=title)
            self.ledger.column(key, width=width, minwidth=60, anchor="w")
        self.ledger.grid(row=0, column=0, sticky="nsew")
        vertical_scroll = ttk.Scrollbar(ledger, orient="vertical", command=self.ledger.yview)
        vertical_scroll.grid(row=0, column=1, sticky="ns")
        horizontal_scroll = ttk.Scrollbar(ledger, orient="horizontal", command=self.ledger.xview)
        horizontal_scroll.grid(row=1, column=0, sticky="ew")
        self.ledger.configure(
            yscrollcommand=vertical_scroll.set,
            xscrollcommand=horizontal_scroll.set,
        )
        self.ledger.tag_configure("position-group", background="#1261a0", foreground="#ffffff")
        self.ledger.tag_configure("position", background="#234d70", foreground="#ffffff")
        self.ledger.tag_configure("payment-group", background="#237a3b", foreground="#ffffff")
        self.ledger.tag_configure("payment", background="#285b3b", foreground="#ffffff")
        self.ledger.tag_configure("reminder-group", background="#b85f00", foreground="#ffffff")
        self.ledger.tag_configure("reminder", background="#70451f", foreground="#ffffff")
        self.ledger.bind("<<TreeviewSelect>>", lambda _event: self._update_reminder_buttons())

        buttons = ttk.Frame(ledger)
        buttons.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(10, 0))
        ttk.Button(buttons, text="Position hinzufügen", command=self._add_item).pack(side="left")
        ttk.Separator(buttons, orient="vertical").pack(side="left", fill="y", padx=10)
        ttk.Button(buttons, text="Vorhandene Zahlung zuordnen", command=self._allocate_payment).pack(
            side="left", padx=(0, 8)
        )
        ttk.Button(buttons, text="Zahlung erfassen", command=self._record_payment).pack(side="left")
        ttk.Separator(buttons, orient="vertical").pack(side="left", fill="y", padx=10)
        self.discard_reminder_button = ttk.Button(
            buttons, text="Entwurf verwerfen", command=self._discard_reminder, state="disabled"
        )
        self.discard_reminder_button.pack(side="right", padx=(8, 0))
        self.send_reminder_button = ttk.Button(
            buttons, text="Als versandt markieren", command=self._send_reminder, state="disabled"
        )
        self.send_reminder_button.pack(side="right", padx=(8, 0))
        ttk.Button(buttons, text="Mahnung vorbereiten", command=self._add_reminder).pack(side="right")
        ttk.Button(buttons, text="Dokument erzeugen", command=self._create_document).pack(
            side="right", padx=(0, 8)
        )

    def refresh(self) -> None:
        try:
            self.data, self.claim = self.repository.get_claim(self.member_id, self.claim_id)
            member = self.repository.get_member(self.member_id)
        except RepositoryError as exc:
            messagebox.showerror("Forderung konnte nicht geladen werden", str(exc), parent=self)
            return
        total = claim_total(self.claim)
        paid = allocated_total(self.data, self.claim_id)
        balance = claim_balance(self.data, self.claim)
        status = claim_status(self.data, self.claim)
        self.title_var.set(str(self.claim.get("title") or "Forderung"))
        due = format_date_for_display(str(self.claim.get("due_date", "")))
        self.subtitle_var.set(
            f"{member.member_number} · {member.display_name} · Fällig: {due or '—'} · {self.claim_id}"
        )
        hold = self.claim.get("dunning_hold") or {}
        if hold.get("active"):
            until = format_date_for_display(str(hold.get("until") or "")) or "unbefristet"
            reason = str(hold.get("reason") or "ohne Begründung")
            self.subtitle_var.set(f"{self.subtitle_var.get()} · MAHNSPERRE bis {until}: {reason}")
        self.summary_vars["total"].set(f"{money_text(total)} EUR")
        self.summary_vars["paid"].set(f"{money_text(paid)} EUR")
        self.summary_vars["balance"].set(f"{money_text(balance)} EUR")
        self.summary_vars["status"].set(CLAIM_STATUS_LABELS.get(status, status.upper()))
        self.cancel_button.configure(state="disabled" if status == "cancelled" else "normal")
        self.hold_button.configure(text="Mahnsperre aufheben" if hold.get("active") else "Mahnsperre setzen")
        self._render_ledger()

    def _render_ledger(self) -> None:
        self.ledger.delete(*self.ledger.get_children())
        items = claim_items(self.claim)
        position_group = self.ledger.insert(
            "",
            "end",
            iid="group:positions",
            text=f"Positionen ({len(items)})",
            values=("", "", "", "", f"{money_text(claim_total(self.claim))} EUR", "", ""),
            tags=("position-group",),
            open=True,
        )
        for item in items:
            self.ledger.insert(
                position_group,
                "end",
                iid=f"item:{item.get('item_id', '')}",
                text=display_label(CLAIM_ITEM_TYPE_LABELS, str(item.get("type", ""))),
                values=(
                    str(item.get("created_at", ""))[:10],
                    item.get("description", ""),
                    item.get("quantity", "1"),
                    item.get("unit_price", item.get("amount", "")),
                    f"{item.get('amount', '')} EUR",
                    "",
                    "",
                ),
                tags=("position",),
            )

        payment_by_id = {str(item.get("payment_id")): item for item in self.data.payments}
        allocations = [
            allocation
            for allocation in self.data.allocations
            if str(allocation.get("claim_id", "")) == self.claim_id
        ]
        payment_group = self.ledger.insert(
            "",
            "end",
            iid="group:payments",
            text=f"Zahlungen ({len(allocations)})",
            values=("", "", "", "", f"-{money_text(allocated_total(self.data, self.claim_id))} EUR", "", ""),
            tags=("payment-group",),
            open=True,
        )
        for allocation in allocations:
            payment = payment_by_id.get(str(allocation.get("payment_id", "")), {})
            payment_total = str(payment.get("amount", ""))
            gnucash_id = str(payment.get("gnucash_transaction_id", ""))
            self.ledger.insert(
                payment_group,
                "end",
                iid=f"allocation:{allocation.get('allocation_id', '')}",
                text="Zahlung",
                values=(
                    format_date_for_display(str(payment.get("date", ""))),
                    payment.get("reference", ""),
                    "",
                    "",
                    f"-{allocation.get('amount', '')} EUR",
                    f"Zahlung gesamt: {payment_total} EUR",
                    f"GnuCash: {gnucash_id}" if gnucash_id else "",
                ),
                tags=("payment",),
            )

        reminders = [
            reminder
            for reminder in self.data.reminders
            if str(reminder.get("claim_id", "")) == self.claim_id
        ]
        reminder_group = self.ledger.insert(
            "",
            "end",
            iid="group:reminders",
            text=f"Mahnungen ({len(reminders)})",
            tags=("reminder-group",),
            open=True,
        )
        for reminder in reminders:
            status = str(reminder.get("status", "draft"))
            deadline = format_date_for_display(str(reminder.get("payment_deadline") or ""))
            channel = display_label(REMINDER_CHANNEL_LABELS, str(reminder.get("channel", "")))
            detail = str(reminder.get("detail", ""))
            self.ledger.insert(
                reminder_group,
                "end",
                iid=f"reminder:{reminder.get('reminder_id', '')}",
                text=f"Mahnstufe {reminder.get('level', '')}",
                values=(
                    str(reminder.get("created_at", ""))[:16],
                    reminder.get("name", f"Mahnung Stufe {reminder.get('level', '')}"),
                    "",
                    "",
                    f"{reminder.get('fee', '0.00')} EUR",
                    display_label(REMINDER_STATUS_LABELS, status),
                    " · ".join(
                        part
                        for part in (channel, f"Frist: {deadline}" if deadline else "", detail)
                        if part
                    ),
                ),
                tags=("reminder",),
            )
        self._update_reminder_buttons()

    def _add_item(self) -> None:
        ItemDialog(self, self.repository, self.member_id, self.claim_id, self._changed)

    def _record_payment(self) -> None:
        PaymentDialog(
            self,
            self.repository,
            self.member_id,
            self.claim_id,
            claim_balance(self.data, self.claim),
            self._changed,
        )

    def _allocate_payment(self) -> None:
        AllocatePaymentDialog(
            self,
            self.repository,
            self.member_id,
            self.claim_id,
            claim_balance(self.data, self.claim),
            self._changed,
        )

    def _add_reminder(self) -> None:
        ReminderDialog(self, self.repository, self.member_id, self.claim_id, self._changed)

    def _create_document(self) -> None:
        reminder = self._selected_reminder()
        DocumentTemplateDialog(
            self,
            self.repository,
            self.member_id,
            self._document_generated,
            claim_id=self.claim_id,
            reminder_id=str(reminder["reminder_id"]) if reminder else None,
        )

    def _document_generated(self, _path: Path) -> None:
        self._changed()

    def _selected_reminder(self) -> dict | None:
        selected = self.ledger.selection()
        if not selected or not selected[0].startswith("reminder:"):
            return None
        reminder_id = selected[0].removeprefix("reminder:")
        return next(
            (item for item in self.data.reminders if str(item.get("reminder_id", "")) == reminder_id),
            None,
        )

    def _update_reminder_buttons(self) -> None:
        reminder = self._selected_reminder()
        editable = bool(reminder and str(reminder.get("status", "draft")) in {"draft", "generated"})
        state = "normal" if editable else "disabled"
        self.send_reminder_button.configure(state=state)
        self.discard_reminder_button.configure(state=state)

    def _send_reminder(self) -> None:
        reminder = self._selected_reminder()
        if not reminder or not messagebox.askyesno(
            "Mahnung versandt",
            "Wurde diese Mahnung tatsächlich versandt? Erst jetzt werden Frist und Gebühr gebucht.",
            parent=self,
        ):
            return
        try:
            self.repository.mark_reminder_sent(self.member_id, self.claim_id, str(reminder["reminder_id"]))
        except RepositoryError as exc:
            messagebox.showerror("Versand konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self._changed()

    def _discard_reminder(self) -> None:
        reminder = self._selected_reminder()
        if not reminder or not messagebox.askyesno(
            "Mahnungsentwurf verwerfen", "Diesen Mahnungsentwurf wirklich verwerfen?", parent=self
        ):
            return
        try:
            self.repository.cancel_reminder(self.member_id, self.claim_id, str(reminder["reminder_id"]))
        except RepositoryError as exc:
            messagebox.showerror("Entwurf konnte nicht verworfen werden", str(exc), parent=self)
            return
        self._changed()

    def _toggle_hold(self) -> None:
        hold = self.claim.get("dunning_hold") or {}
        if hold.get("active"):
            if not messagebox.askyesno("Mahnsperre aufheben", "Mahnsperre wirklich aufheben?", parent=self):
                return
            try:
                self.repository.set_dunning_hold(self.member_id, self.claim_id, active=False)
            except RepositoryError as exc:
                messagebox.showerror("Mahnsperre", str(exc), parent=self)
                return
            self._changed()
            return
        DunningHoldDialog(self, self.repository, self.member_id, self.claim_id, self._changed)

    def _cancel_claim(self) -> None:
        if not messagebox.askyesno(
            "Forderung stornieren", "Diese Forderung wirklich stornieren?", parent=self
        ):
            return
        try:
            self.repository.cancel_claim(self.member_id, self.claim_id)
        except RepositoryError as exc:
            messagebox.showerror("Storno fehlgeschlagen", str(exc), parent=self)
            return
        self._changed()

    def _changed(self) -> None:
        self.refresh()
        self.on_changed()


class _Dialog(tk.Toplevel):
    def __init__(self, master: tk.Misc, title: str, on_saved: Callable[[], None]):
        super().__init__(master)
        self.on_saved = on_saved
        self.title(title)
        self.transient(master.winfo_toplevel())
        self.resizable(False, False)
        self.frame = ttk.Frame(self, padding=18)
        self.frame.pack(fill="both", expand=True)
        self.bind("<Escape>", lambda _event: self.destroy())
        self.after_idle(self.grab_set)

    def _buttons(self, row: int, command: Callable[[], None]) -> None:
        buttons = ttk.Frame(self.frame)
        buttons.grid(row=row, column=0, columnspan=2, sticky="e", pady=(16, 0))
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
        ttk.Button(buttons, text="Speichern", style="Accent.TButton", command=command).pack(side="left")


class ItemDialog(_Dialog):
    def __init__(self, master, repository, member_id, claim_id, on_saved):
        super().__init__(master, "Forderungsposition hinzufügen", on_saved)
        self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
        self.type_var = tk.StringVar(value=CLAIM_ITEM_TYPE_LABELS["correction"])
        self.description_var = tk.StringVar()
        self.quantity_var = tk.StringVar(value="1")
        self.unit_var = tk.StringVar()
        fields = (
            ("Typ", self.type_var),
            ("Beschreibung", self.description_var),
            ("Menge", self.quantity_var),
            ("Einzelpreis", self.unit_var),
        )
        for row, (label, variable) in enumerate(fields):
            ttk.Label(self.frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
            if row == 0:
                ttk.Combobox(
                    self.frame,
                    textvariable=variable,
                    values=list(CLAIM_ITEM_TYPE_LABELS.values()),
                    state="readonly",
                    width=32,
                ).grid(row=row, column=1, sticky="ew", pady=5)
            else:
                ttk.Entry(self.frame, textvariable=variable, width=35).grid(row=row, column=1, pady=5)
        self._buttons(len(fields), self._save)

    def _save(self):
        try:
            self.repository.add_claim_item(
                self.member_id,
                self.claim_id,
                description=self.description_var.get(),
                quantity=self.quantity_var.get(),
                unit_price=self.unit_var.get(),
                item_type=storage_key(CLAIM_ITEM_TYPE_LABELS, self.type_var.get()),
            )
        except RepositoryError as exc:
            messagebox.showerror("Position konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved()


class PaymentDialog(_Dialog):
    def __init__(self, master, repository, member_id, claim_id, balance: Decimal, on_saved):
        super().__init__(master, "Zahlung erfassen", on_saved)
        self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
        initial = money_text(max(balance, Decimal("0")))
        self.variables = {
            "date": tk.StringVar(value=format_date_for_display(date.today().isoformat())),
            "amount": tk.StringVar(value=initial),
            "allocation": tk.StringVar(value=initial),
            "gnucash": tk.StringVar(),
            "reference": tk.StringVar(),
        }
        fields = (
            (f"Zahlungsdatum ({date_input_hint()})", "date"),
            ("Zahlungsbetrag", "amount"),
            ("Dieser Forderung zuordnen", "allocation"),
            ("GnuCash-ID (optional)", "gnucash"),
            ("Referenz", "reference"),
        )
        for row, (label, key) in enumerate(fields):
            ttk.Label(self.frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
            ttk.Entry(self.frame, textvariable=self.variables[key], width=38).grid(row=row, column=1, pady=5)
        self._buttons(len(fields), self._save)

    def _save(self):
        try:
            self.repository.record_payment(
                self.member_id,
                self.claim_id,
                payment_date=self.variables["date"].get(),
                amount=self.variables["amount"].get(),
                allocation_amount=self.variables["allocation"].get(),
                gnucash_transaction_id=self.variables["gnucash"].get(),
                reference=self.variables["reference"].get(),
            )
        except RepositoryError as exc:
            messagebox.showerror("Zahlung konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved()


class AllocatePaymentDialog(_Dialog):
    def __init__(self, master, repository, member_id, claim_id, balance: Decimal, on_saved):
        super().__init__(master, "Vorhandene Zahlung zuordnen", on_saved)
        self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
        data = repository.get_contributions(member_id)
        self.payment_by_label = {}
        for payment in data.payments:
            payment_id = str(payment.get("payment_id", ""))
            available = decimal_value(payment.get("amount", "0")) - payment_allocated_total(data, payment_id)
            if available <= 0:
                continue
            label = (
                f"{payment.get('date', '')} · {money_text(available)} EUR frei · "
                f"{payment.get('reference', '')}"
            )
            self.payment_by_label[label] = (payment_id, available)
        self.payment_var = tk.StringVar()
        self.amount_var = tk.StringVar(value=money_text(max(balance, Decimal("0"))))
        ttk.Label(self.frame, text="Zahlung").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
        combo = ttk.Combobox(
            self.frame,
            textvariable=self.payment_var,
            values=list(self.payment_by_label),
            state="readonly",
            width=60,
        )
        combo.grid(row=0, column=1, pady=5)
        ttk.Label(self.frame, text="Betrag").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(self.frame, textvariable=self.amount_var).grid(row=1, column=1, sticky="ew", pady=5)
        combo.bind("<<ComboboxSelected>>", lambda _event: self._select(balance))
        self._buttons(2, self._save)

    def _select(self, balance):
        _payment_id, available = self.payment_by_label[self.payment_var.get()]
        self.amount_var.set(money_text(min(available, max(balance, Decimal("0")))))

    def _save(self):
        selected = self.payment_by_label.get(self.payment_var.get())
        if not selected:
            messagebox.showerror("Zahlung auswählen", "Bitte eine Zahlung auswählen.", parent=self)
            return
        try:
            self.repository.allocate_payment(
                self.member_id, self.claim_id, payment_id=selected[0], amount=self.amount_var.get()
            )
        except RepositoryError as exc:
            messagebox.showerror("Zuordnung fehlgeschlagen", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved()


class ReminderDialog(_Dialog):
    def __init__(self, master, repository, member_id, claim_id, on_saved):
        super().__init__(master, "Mahnung vorbereiten", on_saved)
        self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
        policy = repository.get_configuration().get("reminder_policy") or {}
        levels = policy.get("levels") or [
            {"level": 1, "name": "Zahlungserinnerung", "fee": "0.00", "payment_deadline_days": 14}
        ]
        self.definition_by_label = {
            f"Stufe {item.get('level', '')}: {item.get('name', '')}": item for item in levels
        }
        self.level_var = tk.StringVar(value=next(iter(self.definition_by_label)))
        self.detail_var = tk.StringVar()
        self.fee_var = tk.StringVar()
        self.deadline_var = tk.StringVar()
        self.channel_var = tk.StringVar(value=REMINDER_CHANNEL_LABELS["email"])
        ttk.Label(self.frame, text="Mahnstufe").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
        level_combo = ttk.Combobox(
            self.frame,
            textvariable=self.level_var,
            values=list(self.definition_by_label),
            state="readonly",
            width=38,
        )
        level_combo.grid(row=0, column=1, sticky="ew", pady=5)
        level_combo.bind("<<ComboboxSelected>>", lambda _event: self._load_definition())
        ttk.Label(self.frame, text="Details").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(self.frame, textvariable=self.detail_var, width=45).grid(row=1, column=1, pady=5)
        ttk.Label(self.frame, text="Mahngebühr").grid(row=2, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(self.frame, textvariable=self.fee_var).grid(row=2, column=1, sticky="ew", pady=5)
        ttk.Label(self.frame, text="Zahlungsfrist in Tagen").grid(
            row=3, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        ttk.Entry(self.frame, textvariable=self.deadline_var).grid(row=3, column=1, sticky="ew", pady=5)
        ttk.Label(self.frame, text="Versandweg").grid(row=4, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Combobox(
            self.frame,
            textvariable=self.channel_var,
            values=list(REMINDER_CHANNEL_LABELS.values()),
            state="readonly",
        ).grid(row=4, column=1, sticky="ew", pady=5)
        self._load_definition()
        self._buttons(5, self._save)

    def _load_definition(self) -> None:
        definition = self.definition_by_label[self.level_var.get()]
        self.fee_var.set(str(definition.get("fee", "0.00")))
        self.deadline_var.set(str(definition.get("payment_deadline_days", 14)))

    def _save(self):
        definition = self.definition_by_label[self.level_var.get()]
        try:
            self.repository.create_reminder_draft(
                self.member_id,
                self.claim_id,
                level=int(definition.get("level", 1)),
                name=str(definition.get("name", "Mahnung")),
                payment_deadline_days=int(self.deadline_var.get()),
                detail=self.detail_var.get(),
                fee=self.fee_var.get(),
                channel=storage_key(REMINDER_CHANNEL_LABELS, self.channel_var.get()),
            )
        except (ValueError, RepositoryError) as exc:
            messagebox.showerror("Mahnungsentwurf konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved()


class DunningHoldDialog(_Dialog):
    def __init__(self, master, repository, member_id, claim_id, on_saved):
        super().__init__(master, "Mahnsperre setzen", on_saved)
        self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
        self.reason_var = tk.StringVar()
        self.until_var = tk.StringVar()
        ttk.Label(self.frame, text="Grund").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(self.frame, textvariable=self.reason_var, width=45).grid(row=0, column=1, pady=5)
        ttk.Label(self.frame, text=f"Bis ({date_input_hint()}, optional)").grid(
            row=1, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        ttk.Entry(self.frame, textvariable=self.until_var).grid(row=1, column=1, sticky="ew", pady=5)
        self._buttons(2, self._save)

    def _save(self) -> None:
        try:
            self.repository.set_dunning_hold(
                self.member_id,
                self.claim_id,
                active=True,
                reason=self.reason_var.get(),
                until=self.until_var.get(),
            )
        except RepositoryError as exc:
            messagebox.showerror("Mahnsperre konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved()
