from __future__ import annotations

import tkinter as tk
from collections.abc import Callable
from decimal import Decimal
from email.parser import BytesParser
from email.policy import default as default_policy
from tkinter import filedialog, messagebox, ttk

from ccma.domain.contributions import claim_balance, money_text
from ccma.domain.dates import format_date_for_display
from ccma.services.welcome_mail import (
    generate_and_send_welcome_mail,
    open_claims,
    welcome_mail_bytes,
)
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.email_delivery_dialog import resolve_delivery_mode


class WelcomeMailDialog(tk.Toplevel):
    """Sends the welcome mail that doubles as the first invoice for a new member:
    membership start, member number and every claim that is still open."""

    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        member_id: str,
        on_sent: Callable[[], None],
    ):
        super().__init__(master)
        self.repository = repository
        self.member_id = member_id
        self.on_sent = on_sent
        self.claims: list[dict] = []
        organization = repository.get_configuration().get("organization") or {}
        self.sender_name_var = tk.StringVar(value=str(organization.get("name") or "Verwaltung C3MA"))
        self.sender_email_var = tk.StringVar(value=str(organization.get("email") or ""))
        self.summary_var = tk.StringVar()
        self.title("Willkommens- und Erstrechnungsmail")
        self.transient(master.winfo_toplevel())
        self.resizable(True, True)
        self._build_ui()
        self._refresh_claims()
        self.grab_set()

    def _build_ui(self) -> None:
        self.columnconfigure(0, weight=1)
        self.rowconfigure(2, weight=1)
        ttk.Label(
            self,
            text=(
                "Begrüßt das neue Mitglied und stellt die noch offenen Forderungen "
                "(Aufnahmegebühr und erster Beitrag) als Erstrechnung zusammen. Text und "
                "Betreff stammen aus der Vorlage unter Optionen → E-Mail-Vorlagen."
            ),
            wraplength=640,
        ).grid(row=0, column=0, sticky="w", padx=14, pady=(14, 8))
        ttk.Label(
            self,
            text=(
                "Standardmäßig sind alle offenen Forderungen ausgewählt – einzelne mit "
                "Strg+Klick abwählen."
            ),
            style="Muted.TLabel",
            wraplength=640,
        ).grid(row=1, column=0, sticky="w", padx=14, pady=(0, 8))
        table = ttk.Frame(self)
        table.grid(row=2, column=0, sticky="nsew", padx=14)
        table.columnconfigure(0, weight=1)
        table.rowconfigure(0, weight=1)
        self.table = ttk.Treeview(
            table,
            columns=("due", "amount", "title"),
            show="headings",
            selectmode="extended",
            height=6,
        )
        for key, title, width in (
            ("due", "Fällig", 90),
            ("amount", "Offen", 90),
            ("title", "Forderung", 320),
        ):
            self.table.heading(key, text=title)
            self.table.column(key, width=width, anchor="w", stretch=key == "title")
        self.table.grid(row=0, column=0, sticky="nsew")
        scroll = ttk.Scrollbar(table, orient="vertical", command=self.table.yview)
        scroll.grid(row=0, column=1, sticky="ns")
        self.table.configure(yscrollcommand=scroll.set)
        self.table.bind("<<TreeviewSelect>>", lambda _event: self._update_summary())
        ttk.Label(self, textvariable=self.summary_var, style="Mono.TLabel").grid(
            row=3, column=0, sticky="w", padx=14, pady=(6, 10)
        )

        fields = ttk.Frame(self)
        fields.grid(row=4, column=0, sticky="ew", padx=14)
        fields.columnconfigure(1, weight=1)
        ttk.Label(fields, text="Absendername").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(fields, textvariable=self.sender_name_var, width=52).grid(row=0, column=1, sticky="ew")
        ttk.Label(fields, text="Absenderadresse").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
        ttk.Entry(fields, textvariable=self.sender_email_var).grid(row=1, column=1, sticky="ew")
        ttk.Label(fields, text="Signatur").grid(row=2, column=0, sticky="nw", pady=5, padx=(0, 12))
        self.signature = tk.Text(fields, width=52, height=4, wrap="word")
        self.signature.grid(row=2, column=1, sticky="ew", pady=5)
        self.signature.insert("1.0", "Der Vorstand\nChaos Computer Club Mannheim e.V.")

        buttons = ttk.Frame(self, padding=14)
        buttons.grid(row=5, column=0, sticky="e")
        ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left")
        ttk.Button(buttons, text="Vorschau", command=self._preview).pack(side="left", padx=(8, 0))
        ttk.Button(
            buttons, text="E-Mail erzeugen", style="Accent.TButton", command=self._generate
        ).pack(side="left", padx=(8, 0))

    def _refresh_claims(self) -> None:
        try:
            self.claims = open_claims(self.repository, self.member_id)
            data = self.repository.get_contributions(self.member_id)
        except RepositoryError as exc:
            messagebox.showerror("Forderungen konnten nicht gelesen werden", str(exc), parent=self)
            return
        self.table.delete(*self.table.get_children())
        for index, claim in enumerate(self.claims):
            self.table.insert(
                "",
                "end",
                iid=str(index),
                values=(
                    format_date_for_display(str(claim.get("due_date", ""))),
                    money_text(claim_balance(data, claim)),
                    str(claim.get("description", "") or claim.get("title", "")),
                ),
            )
        self.table.selection_set(self.table.get_children())
        self._update_summary()

    def _selected_claims(self) -> list[dict]:
        return [self.claims[int(iid)] for iid in self.table.selection()]

    def _update_summary(self) -> None:
        data = self.repository.get_contributions(self.member_id)
        selected = self._selected_claims()
        total = sum((claim_balance(data, claim) for claim in selected), Decimal("0"))
        self.summary_var.set(
            f"{len(selected)} von {len(self.claims)} Forderungen ausgewählt – "
            f"Gesamtbetrag {money_text(total)} Euro"
        )

    def _mail_bytes(self) -> bytes:
        member = self.repository.get_member(self.member_id)
        data = self.repository.get_contributions(self.member_id)
        claims = self._selected_claims()
        return welcome_mail_bytes(
            self.repository,
            member=member,
            claims=claims,
            balances={
                str(claim.get("claim_id", "")): claim_balance(data, claim) for claim in claims
            },
            organization=self.repository.get_configuration().get("organization") or {},
            sender_name=self.sender_name_var.get(),
            sender_email=self.sender_email_var.get(),
            signature=self.signature.get("1.0", "end-1c"),
        )

    def _preview(self) -> None:
        try:
            message = BytesParser(policy=default_policy).parsebytes(self._mail_bytes())
        except (OSError, RepositoryError) as exc:
            messagebox.showerror("Vorschau nicht möglich", str(exc), parent=self)
            return
        window = tk.Toplevel(self)
        window.title("Vorschau der Willkommensmail")
        window.transient(self)
        window.columnconfigure(0, weight=1)
        window.rowconfigure(1, weight=1)
        ttk.Label(window, text=f"Betreff: {message['Subject']}", padding=12).grid(
            row=0, column=0, sticky="w"
        )
        preview = tk.Text(window, wrap="word", width=88, height=28)
        preview.grid(row=1, column=0, sticky="nsew", padx=12, pady=(0, 12))
        preview.insert("1.0", message.get_content())
        preview.configure(state="disabled")
        scroll = ttk.Scrollbar(window, orient="vertical", command=preview.yview)
        scroll.grid(row=1, column=1, sticky="ns", pady=(0, 12), padx=(0, 12))
        preview.configure(yscrollcommand=scroll.set)
        ttk.Button(window, text="Schließen", command=window.destroy).grid(
            row=2, column=0, columnspan=2, sticky="e", padx=12, pady=(0, 12)
        )

    def _generate(self) -> None:
        selected = self._selected_claims()
        if not selected and not messagebox.askyesno(
            "Keine Forderung ausgewählt",
            "Es ist keine Forderung ausgewählt. Soll die Mail ohne Rechnungspositionen "
            "erzeugt werden?",
            parent=self,
        ):
            return
        delivery_mode = resolve_delivery_mode(self, self.repository)
        if delivery_mode is None:
            return
        output_path = None
        if delivery_mode == "local":
            chosen = filedialog.asksaveasfilename(
                parent=self,
                title="Willkommensmail als Thunderbird-Entwurf speichern",
                defaultextension=".eml",
                initialfile="Willkommen.eml",
                filetypes=[("E-Mail-Datei", "*.eml"), ("Alle Dateien", "*.*")],
            )
            if not chosen:
                return
            output_path = chosen
        try:
            result = generate_and_send_welcome_mail(
                self.repository,
                self.member_id,
                claim_ids=[str(claim.get("claim_id", "")) for claim in selected],
                delivery_mode=delivery_mode,
                output_path=output_path,
                sender_name=self.sender_name_var.get(),
                sender_email=self.sender_email_var.get(),
                signature=self.signature.get("1.0", "end-1c"),
            )
        except (OSError, RepositoryError) as exc:
            messagebox.showerror("E-Mail konnte nicht erzeugt werden", str(exc), parent=self)
            return
        outcome = {
            "local": f"gespeichert:\n{result.export_path}",
            "send": "direkt versandt.",
            "drafts": "als Entwurf im Postfach abgelegt.",
        }[delivery_mode]
        messagebox.showinfo(
            "Willkommensmail erzeugt",
            f"Die E-Mail wurde {outcome}\n\nEine Kopie liegt in der Mitgliederakte.",
            parent=self,
        )
        self.destroy()
        self.on_sent()
