from __future__ import annotations

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

from ccma.domain.models import MEMBERSHIP_STATUS_LABELS, Member
from ccma.services.member_data_mail import (
    data_review_mail_bytes,
    data_review_recipients,
    generate_data_review_mails,
)
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.email_delivery_dialog import resolve_delivery_mode


class DataReviewMailDialog(tk.Toplevel):
    """Asks the members to check the data the club stores about them. Unlike the
    other mails this one goes out to everybody at once, so the recipients are picked
    from a list every member appears in -- with the active ones preselected."""

    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        on_sent: Callable[[], None] | None = None,
    ):
        super().__init__(master)
        self.repository = repository
        self.on_sent = on_sent
        self.members: list[Member] = []
        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.warning_var = tk.StringVar()
        self.title("Datenüberprüfung anfragen")
        self.transient(master.winfo_toplevel())
        self.geometry("940x620")
        self.minsize(760, 480)
        self._build_ui()
        self._refresh_members()
        self.after_idle(self.grab_set)

    def _build_ui(self) -> None:
        self.columnconfigure(0, weight=1)
        self.rowconfigure(3, weight=1)
        ttk.Label(
            self,
            text=(
                "Bittet die ausgewählten Mitglieder, die über sie gespeicherten Daten zu "
                "prüfen. Jede Mail listet den Datensatz genau eines Mitglieds auf; die IBAN "
                "erscheint dabei nur mit den letzten vier Stellen. Betreff und Text stammen "
                "aus der Vorlage unter Optionen → E-Mail-Vorlagen."
            ),
            wraplength=880,
        ).grid(row=0, column=0, sticky="w", padx=14, pady=(14, 8))
        ttk.Label(
            self,
            text=(
                "Vorausgewählt sind alle aktiven Mitgliedschaften mit E-Mail-Adresse – "
                "einzelne mit Strg+Klick ab- oder zusätzlich auswählen."
            ),
            style="Muted.TLabel",
            wraplength=880,
        ).grid(row=1, column=0, sticky="w", padx=14, pady=(0, 8))
        ttk.Label(self, textvariable=self.summary_var, style="Mono.TLabel").grid(
            row=2, column=0, sticky="w", padx=14, pady=(0, 6)
        )
        table = ttk.Frame(self)
        table.grid(row=3, column=0, sticky="nsew", padx=14)
        table.columnconfigure(0, weight=1)
        table.rowconfigure(0, weight=1)
        self.table = ttk.Treeview(
            table,
            columns=("number", "name", "status", "email"),
            show="headings",
            selectmode="extended",
        )
        for key, title, width, stretch in (
            ("number", "Nummer", 90, False),
            ("name", "Name", 220, True),
            ("status", "Status", 200, False),
            ("email", "E-Mail", 260, True),
        ):
            self.table.heading(key, text=title)
            self.table.column(key, width=width, anchor="w", stretch=stretch)
        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())

        fields = ttk.Frame(self)
        fields.grid(row=4, column=0, sticky="ew", padx=14, pady=(10, 0))
        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.")

        footer = ttk.Frame(self, padding=14)
        footer.grid(row=5, column=0, sticky="ew")
        footer.columnconfigure(0, weight=1)
        ttk.Label(footer, textvariable=self.warning_var, style="Muted.TLabel", wraplength=520).grid(
            row=0, column=0, sticky="w"
        )
        ttk.Button(footer, text="Abbrechen", command=self.destroy).grid(row=0, column=1, padx=(8, 0))
        ttk.Button(footer, text="Vorschau", command=self._preview).grid(row=0, column=2, padx=(8, 0))
        ttk.Button(
            footer, text="Mails erzeugen", style="Accent.TButton", command=self._generate
        ).grid(row=0, column=3, padx=(8, 0))

    def _refresh_members(self) -> None:
        try:
            self.members = self.repository.list_members()
            preselected = {
                member.member_id for member in data_review_recipients(self.repository)
            }
        except RepositoryError as exc:
            messagebox.showerror("Mitglieder konnten nicht gelesen werden", str(exc), parent=self)
            return
        self.table.delete(*self.table.get_children())
        for index, member in enumerate(self.members):
            self.table.insert(
                "",
                "end",
                iid=str(index),
                values=(
                    member.member_number,
                    member.display_name,
                    MEMBERSHIP_STATUS_LABELS.get(member.status, member.status),
                    member.email or "—",
                ),
            )
        self.table.selection_set(
            [
                str(index)
                for index, member in enumerate(self.members)
                if member.member_id in preselected
            ]
        )
        without_email = [member for member in self.members if not member.email.strip()]
        self.warning_var.set(
            f"{len(without_email)} Mitglied(er) ohne E-Mail-Adresse werden übersprungen."
            if without_email
            else ""
        )
        self._update_summary()

    def _selected_members(self) -> list[Member]:
        return [self.members[int(iid)] for iid in sorted(self.table.selection(), key=int)]

    def _update_summary(self) -> None:
        selected = self._selected_members()
        reachable = [member for member in selected if member.email.strip()]
        self.summary_var.set(
            f"{len(selected)} von {len(self.members)} Mitgliedern ausgewählt – "
            f"{len(reachable)} Mail(s) werden erzeugt"
        )

    def _preview(self) -> None:
        selected = self._selected_members()
        member = next((item for item in selected if item.email.strip()), None)
        if member is None:
            messagebox.showinfo(
                "Keine Vorschau möglich",
                "Bitte mindestens ein Mitglied mit E-Mail-Adresse auswählen.",
                parent=self,
            )
            return
        try:
            content = data_review_mail_bytes(
                self.repository,
                member=member,
                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"),
            )
        except (OSError, RepositoryError) as exc:
            messagebox.showerror("Vorschau nicht möglich", str(exc), parent=self)
            return
        message = BytesParser(policy=default_policy).parsebytes(content)
        window = tk.Toplevel(self)
        window.title(f"Vorschau für {member.display_name}")
        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=30)
        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:
        recipients = [member for member in self._selected_members() if member.email.strip()]
        if not recipients:
            messagebox.showinfo(
                "Keine Empfänger",
                "Bitte mindestens ein Mitglied mit E-Mail-Adresse auswählen.",
                parent=self,
            )
            return
        delivery_mode = resolve_delivery_mode(self, self.repository)
        if delivery_mode is None:
            return
        output_directory = None
        if delivery_mode == "local":
            selected = filedialog.askdirectory(
                parent=self,
                title="Ausgabeordner für die Thunderbird-Entwürfe auswählen",
                mustexist=True,
            )
            if not selected:
                return
            output_directory = selected
        # A mailing to the whole club cannot be taken back, so the count is confirmed
        # once more -- especially when "Senden" would put it on the wire right away.
        action = {
            "local": "als Datei abgelegt",
            "send": "sofort versandt",
            "drafts": "als Entwurf im Postfach abgelegt",
        }[delivery_mode]
        if not messagebox.askyesno(
            "Datenüberprüfung verschicken",
            f"An {len(recipients)} Mitglied(er) wird je eine Mail mit deren gespeicherten "
            f"Daten {action}.\n\nJetzt erzeugen?",
            parent=self,
        ):
            return
        self.configure(cursor="watch")
        self.update_idletasks()
        try:
            generated, warnings = generate_data_review_mails(
                self.repository,
                [member.member_id for member in recipients],
                delivery_mode=delivery_mode,
                output_directory=output_directory,
                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("Mails konnten nicht erzeugt werden", str(exc), parent=self)
            return
        finally:
            self.configure(cursor="")
        location = {
            "local": f"in\n{output_directory}\nerzeugt",
            "send": "versandt",
            "drafts": "als Entwurf im Postfach abgelegt",
        }[delivery_mode]
        detail = (
            f"{len(generated)} Mail(s) wurden {location} und in den Mitgliederakten archiviert."
        )
        if warnings:
            detail += "\n\nÜbersprungen:\n" + "\n".join(warnings[:12])
            if len(warnings) > 12:
                detail += f"\n… und {len(warnings) - 12} weitere."
        messagebox.showinfo("Datenüberprüfung verschickt", detail, parent=self)
        self.destroy()
        if self.on_sent is not None:
            self.on_sent()
