from __future__ import annotations

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

from ccma.domain.contributions import (
    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


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 DonationEditDialog(_Dialog):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        member_id: str,
        on_saved: Callable[[], None],
        donation: dict | None = None,
    ):
        super().__init__(
            master, "Spende bearbeiten" if donation else "Spende anlegen", on_saved
        )
        self.repository = repository
        self.member_id = member_id
        self.donation_id = str(donation.get("donation_id")) if donation else None
        initial_date = str(donation.get("date", "")) if donation else date.today().isoformat()
        self.variables = {
            "date": tk.StringVar(value=format_date_for_display(initial_date)),
            "amount": tk.StringVar(value=str(donation.get("amount", "")) if donation else ""),
            "reference": tk.StringVar(value=str(donation.get("reference", "")) if donation else ""),
            "purpose": tk.StringVar(value=str(donation.get("purpose", "")) if donation else ""),
        }
        fields = (
            (f"Spendendatum ({date_input_hint()})", "date"),
            ("Betrag", "amount"),
            ("Referenz", "reference"),
            ("Verwendungszweck", "purpose"),
        )
        self.frame.columnconfigure(1, weight=1)
        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=42).grid(
                row=row, column=1, sticky="ew", pady=5
            )
        self._buttons(len(fields), self._save)

    def _save(self) -> None:
        try:
            if self.donation_id:
                self.repository.update_donation(
                    self.member_id,
                    self.donation_id,
                    donation_date=self.variables["date"].get(),
                    amount=self.variables["amount"].get(),
                    reference=self.variables["reference"].get(),
                    purpose=self.variables["purpose"].get(),
                )
            else:
                self.repository.record_donation(
                    self.member_id,
                    donation_date=self.variables["date"].get(),
                    amount=self.variables["amount"].get(),
                    reference=self.variables["reference"].get(),
                    purpose=self.variables["purpose"].get(),
                )
        except RepositoryError as exc:
            messagebox.showerror("Spende konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.destroy()
        self.on_saved()


class DonationPaymentDialog(_Dialog):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        member_id: str,
        donation_id: str,
        balance: Decimal,
        on_saved: Callable[[], None],
    ):
        super().__init__(master, "Zahlung für Spende erfassen", on_saved)
        self.repository, self.member_id, self.donation_id = repository, member_id, donation_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 Spende zuordnen", "allocation"),
            ("GnuCash-ID (optional)", "gnucash"),
            ("Referenz", "reference"),
        )
        self.frame.columnconfigure(1, weight=1)
        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) -> None:
        try:
            self.repository.record_donation_payment(
                self.member_id,
                self.donation_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 AllocateDonationPaymentDialog(_Dialog):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        member_id: str,
        donation_id: str,
        balance: Decimal,
        on_saved: Callable[[], None],
    ):
        super().__init__(master, "Vorhandene Zahlung zuordnen", on_saved)
        self.repository, self.member_id, self.donation_id = repository, member_id, donation_id
        data = repository.get_contributions(member_id)
        self.payment_by_label = {}
        for payment in sorted(
            data.payments,
            key=lambda item: (str(item.get("date", "")), str(item.get("created_at", ""))),
            reverse=True,
        ):
            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"))))
        self.frame.columnconfigure(1, weight=1)
        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)
        if not self.payment_by_label:
            ttk.Label(
                self.frame,
                text="Für dieses Mitglied gibt es keine Zahlung mit freiem Restbetrag.",
                style="Mono.TLabel",
            ).grid(row=1, column=0, columnspan=2, sticky="w", pady=(3, 5))
            amount_row = 2
        else:
            amount_row = 1
        ttk.Label(self.frame, text="Betrag").grid(row=amount_row, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(self.frame, textvariable=self.amount_var).grid(
            row=amount_row, column=1, sticky="ew", pady=5
        )
        combo.bind("<<ComboboxSelected>>", lambda _event: self._select(balance))
        self._buttons(amount_row + 1, self._save)

    def _select(self, balance: Decimal) -> None:
        _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) -> None:
        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_to_donation(
                self.member_id, self.donation_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()
