from __future__ import annotations

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

from ccma.domain.models import ASSET_STATUS_LABELS, Event
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.dialogs import AssetClaimDialog, IntegrityWarningDialog
from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage
from ccma.ui.scrolling import ScrollableFrame


class AssetTab(ttk.Frame):
    def __init__(
        self,
        master: tk.Misc,
        repository: MemberRepository,
        asset_id: str,
        on_close: Callable[[], None],
        on_changed: Callable[[], None],
        on_open_member: Callable[[str], None],
        on_issue_asset: Callable[[str], None],
        on_return_asset: Callable[[str], None],
        on_open_claim: Callable[[str, str], None],
    ):
        super().__init__(master, padding=12)
        self.repository = repository
        self.asset_id = asset_id
        self.on_close = on_close
        self.on_changed = on_changed
        self.on_open_member = on_open_member
        self.on_issue_asset = on_issue_asset
        self.on_return_asset = on_return_asset
        self.on_open_claim = on_open_claim
        self.asset = repository.get_asset(asset_id)
        self.variables: dict[str, tk.StringVar] = {}
        self.notes_text: tk.Text | None = None
        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, 10))
        header.columnconfigure(0, weight=1)
        header.columnconfigure(1, weight=0)
        self.title_var = tk.StringVar()
        self.subtitle_var = tk.StringVar()
        self.id_var = tk.StringVar()
        title_column = ttk.Frame(header)
        title_column.grid(row=0, column=0, sticky="ew")
        title_column.columnconfigure(0, weight=1)
        ttk.Label(title_column, textvariable=self.title_var, style="TabTitle.TLabel").grid(
            row=0, column=0, sticky="w"
        )
        ttk.Label(title_column, textvariable=self.subtitle_var, style="Mono.TLabel").grid(
            row=1, column=0, sticky="w"
        )
        ttk.Label(title_column, textvariable=self.id_var, style="Mono.TLabel").grid(
            row=2, column=0, sticky="w", pady=(3, 0)
        )
        ttk.Button(header, text="Tab schließen", command=self.on_close).grid(
            row=0, column=1, sticky="ne", padx=(12, 0)
        )
        self.messages = MessageBannerList(self)
        self.messages.grid(row=1, column=0, sticky="ew", pady=(0, 10))
        self.messages.grid_remove()

        self.pane = ttk.Panedwindow(self, orient="horizontal")
        self.pane.grid(row=2, column=0, sticky="nsew")
        self.details_pane = ttk.Frame(self.pane, padding=(0, 0, 10, 0))
        self.timeline_pane = ttk.Frame(self.pane, padding=(10, 0, 0, 0))
        self.pane.add(self.details_pane, weight=2)
        self.pane.add(self.timeline_pane, weight=3)
        self._build_details(self.details_pane)
        self._build_timeline(self.timeline_pane)
        self._pane_position_initialized = False
        self.pane.bind("<Configure>", self._set_initial_pane_position, add="+")

    def _set_initial_pane_position(self, event: tk.Event | None = None) -> None:
        if self._pane_position_initialized:
            return
        try:
            width = int(getattr(event, "width", 0)) or self.pane.winfo_width()
            if width > 1:
                self.pane.sashpos(0, max(360, int(width * 0.4)))
                self._pane_position_initialized = True
        except tk.TclError:
            return

    def _build_details(self, parent: ttk.Frame) -> None:
        parent.columnconfigure(0, weight=1)
        parent.rowconfigure(1, weight=1)
        ttk.Label(parent, text="// ASSET", style="TimelineHeader.TLabel").grid(
            row=0, column=0, sticky="w", pady=(0, 8)
        )
        notebook = ttk.Notebook(parent)
        notebook.grid(row=1, column=0, sticky="nsew")
        data_tab = self._create_form_tab(notebook, "Stammdaten")
        actions = ttk.Frame(parent)
        actions.grid(row=2, column=0, sticky="ew", pady=(10, 0))
        actions.columnconfigure(0, weight=1)
        action_buttons = ttk.Frame(actions)
        action_buttons.grid(row=0, column=0, sticky="e")
        finance_tab = ttk.Frame(notebook, padding=16)
        notebook.add(finance_tab, text="Forderungen")

        fields = [
            ("UUID / Ordner-ID", "asset_id"),
            ("Bezeichnung", "label"),
            ("Kategorie", "category"),
            ("Inventarnummer", "inventory_number"),
            ("Seriennummer", "serial_number"),
            ("Kaution (EUR)", "deposit_amount_default"),
            ("Standort", "location"),
            ("Übergabedatum", "handed_over_at"),
            ("Zustand bei Übergabe", "condition"),
            ("Wert / Kaufpreis", "estimated_value"),
        ]
        for row, (label, key) in enumerate(fields):
            self.variables[key] = tk.StringVar()
            ttk.Label(data_tab, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
            entry = ttk.Entry(data_tab, textvariable=self.variables[key], width=42)
            entry.grid(row=row, column=1, sticky="ew", pady=5)
            if key == "asset_id":
                entry.configure(state="readonly")
            if key == "deposit_amount_default":
                self.deposit_entry = entry
        self.variables["status"] = tk.StringVar()
        holder_row = len(fields)
        ttk.Label(data_tab, text="Status").grid(row=holder_row, column=0, sticky="w", pady=5, padx=(0, 12))
        self.status_box = ttk.Combobox(
            data_tab,
            textvariable=self.variables["status"],
            values=["VERFUEGBAR", "AUSGEGEBEN", "VERLOREN", "AUSGEMUSTERT"],
            state="readonly",
            width=39,
        )
        self.status_box.grid(row=holder_row, column=1, sticky="ew", pady=5)
        self.holder_var = tk.StringVar()
        ttk.Label(data_tab, text="Aktueller Halter").grid(
            row=holder_row + 1,
            column=0,
            sticky="w",
            pady=5,
            padx=(0, 12),
        )
        self.holder_label = ttk.Label(
            data_tab,
            textvariable=self.holder_var,
            style="TimelineHeader.TLabel",
        )
        self.holder_label.grid(row=holder_row + 1, column=1, sticky="w", pady=5)
        self.holder_label.bind("<Button-1>", lambda _event: self._open_holder_member(), add="+")
        self.owner_var = tk.StringVar()
        ttk.Label(data_tab, text="Eigentümer").grid(
            row=holder_row + 2, column=0, sticky="w", pady=5, padx=(0, 12)
        )
        self.owner_label = ttk.Label(data_tab, textvariable=self.owner_var, style="TimelineHeader.TLabel")
        self.owner_label.grid(row=holder_row + 2, column=1, sticky="w", pady=5)
        self.owner_label.bind("<Button-1>", lambda _event: self._open_owner_member(), add="+")
        ttk.Label(data_tab, text="Interne Notiz").grid(
            row=holder_row + 3,
            column=0,
            sticky="nw",
            pady=5,
            padx=(0, 12),
        )
        self.notes_text = tk.Text(data_tab, width=42, height=6, wrap="word")
        self.notes_text.grid(row=holder_row + 3, column=1, sticky="ew", pady=5)
        data_tab.columnconfigure(1, weight=1)
        self.issue_button = ttk.Button(
            action_buttons,
            text="Ausgeben",
            command=lambda: self.on_issue_asset(self.asset_id),
        )
        self.issue_button.pack(side="left", padx=(0, 8))
        self.return_button = ttk.Button(
            action_buttons,
            text="Zurücknehmen",
            command=lambda: self.on_return_asset(self.asset_id),
        )
        self.return_button.pack(side="left", padx=(0, 8))
        ttk.Button(
            action_buttons,
            text="Speichern",
            style="Accent.TButton",
            command=self._save,
        ).pack(side="left")

        finance_tab.columnconfigure(0, weight=1)
        self.finance_summary_var = tk.StringVar()
        ttk.Label(finance_tab, textvariable=self.finance_summary_var, style="Mono.TLabel").grid(
            row=0, column=0, sticky="w", pady=(0, 10)
        )
        finance_actions = ttk.Frame(finance_tab)
        finance_actions.grid(row=1, column=0, sticky="ew")
        ttk.Button(finance_actions, text="Kautionsforderung", command=self._create_deposit_claim).pack(
            side="left", padx=(0, 8)
        )
        ttk.Button(finance_actions, text="Kautionsrückzahlung", command=self._create_refund_claim).pack(
            side="left", padx=(0, 8)
        )
        ttk.Button(finance_actions, text="Verlustforderung", command=self._create_loss_claim).pack(
            side="left", padx=(0, 8)
        )
        ttk.Button(
            finance_actions,
            text="Reparaturforderung",
            command=self._create_repair_claim,
        ).pack(side="left")
        self.asset_claims = ttk.Treeview(
            finance_tab,
            columns=("title", "due", "amount", "member"),
            show="headings",
        )
        for key, title, width in (
            ("title", "Forderung", 240),
            ("due", "Fällig", 110),
            ("amount", "Betrag", 90),
            ("member", "Mitglied", 220),
        ):
            self.asset_claims.heading(key, text=title)
            self.asset_claims.column(key, width=width, anchor="w")
        self.asset_claims.grid(row=2, column=0, sticky="nsew", pady=(10, 0))
        finance_tab.rowconfigure(2, weight=1)
        self.asset_claims.bind("<Double-1>", lambda _event: self._open_selected_asset_claim())
        self.asset_claims.bind("<Return>", lambda _event: self._open_selected_asset_claim())

    def _create_form_tab(self, notebook: ttk.Notebook, title: str) -> ttk.Frame:
        tab = ttk.Frame(notebook)
        tab.columnconfigure(0, weight=1)
        tab.rowconfigure(0, weight=1)
        scroller = ScrollableFrame(tab, padding=16)
        scroller.grid(row=0, column=0, sticky="nsew")
        notebook.add(tab, text=title)
        return scroller.content

    def _build_timeline(self, parent: ttk.Frame) -> None:
        parent.columnconfigure(0, weight=1)
        parent.rowconfigure(1, weight=1)
        ttk.Label(parent, text="// CHRONIK", style="TimelineHeader.TLabel").grid(
            row=0, column=0, sticky="w", pady=(0, 8)
        )
        self.timeline = ttk.Treeview(
            parent, columns=("time", "summary"), show="headings", style="Timeline.Treeview"
        )
        self.timeline.heading("time", text="Zeit")
        self.timeline.heading("summary", text="Ereignis")
        self.timeline.column("time", width=135, stretch=False)
        self.timeline.column("summary", width=320, stretch=True)
        self.timeline.grid(row=1, column=0, sticky="nsew")
        compose = ttk.Frame(parent)
        compose.grid(row=2, column=0, sticky="ew", pady=(10, 0))
        compose.columnconfigure(0, weight=1)
        self.comment_var = tk.StringVar()
        comment = ttk.Entry(compose, textvariable=self.comment_var)
        comment.grid(row=0, column=0, sticky="ew", padx=(0, 6))
        comment.bind("<Return>", lambda _event: self._add_comment())
        ttk.Button(compose, text="Kommentar", command=self._add_comment).grid(row=0, column=1)

    def refresh(self) -> None:
        self.asset = self.repository.get_asset(self.asset_id)
        self.title_var.set(self.asset.label or self.asset.asset_id)
        holder = self._holder_label()
        status = ASSET_STATUS_LABELS.get(self.asset.status, self.asset.status.upper())
        self.subtitle_var.set(f"{self.asset.inventory_number or '—'} · {status} · {holder}")
        self.id_var.set(f"UUID: {self.asset.asset_id}")
        warnings = self.repository.asset_hash_warnings(self.asset_id)
        self.messages.set_messages(
            [
                TabMessage(
                    "warning",
                    "WARNUNG: " + " | ".join(warnings),
                    MessageAction("Überprüft, bestätigen", self._confirm_integrity_banner),
                )
            ]
            if warnings
            else []
        )
        self.variables["asset_id"].set(self.asset.asset_id)
        self.variables["label"].set(self.asset.label)
        self.variables["category"].set(self.asset.category)
        self.variables["inventory_number"].set(self.asset.inventory_number)
        self.variables["serial_number"].set(self.asset.serial_number)
        self.variables["deposit_amount_default"].set(self.asset.deposit_amount_default)
        self.variables["location"].set(self.asset.location)
        self.variables["handed_over_at"].set(self.asset.handed_over_at)
        self.variables["condition"].set(self.asset.condition)
        self.variables["estimated_value"].set(self.asset.estimated_value)
        self.variables["status"].set(ASSET_STATUS_LABELS.get(self.asset.status, self.asset.status.upper()))
        if self.notes_text is not None:
            self.notes_text.delete("1.0", "end")
            self.notes_text.insert("1.0", self.asset.notes)
        self.holder_var.set(holder)
        self.owner_var.set(self._owner_label())
        issued = bool(self.asset.current_holder_member_id)
        self.holder_label.configure(cursor="hand2" if issued else "")
        self.owner_label.configure(cursor="hand2" if self.asset.owner_type == "member" else "")
        self.issue_button.configure(state="normal" if self.asset.status == "available" else "disabled")
        self.return_button.configure(state="normal" if issued else "disabled")
        self.status_box.configure(state="disabled" if issued else "readonly")
        deposit_state = "readonly" if issued else "normal"
        self.deposit_entry.configure(state=deposit_state)
        self._refresh_events()
        self._refresh_finance()

    def _holder_label(self) -> str:
        if not self.asset.current_holder_member_id:
            return "—"
        try:
            member = self.repository.get_member(self.asset.current_holder_member_id)
        except RepositoryError:
            return self.asset.current_holder_member_id
        return f"{member.member_number or member.member_id} · {member.display_name}"

    def _owner_label(self) -> str:
        if self.asset.owner_type == "club":
            return "Verein"
        if self.asset.owner_type == "external":
            return self.asset.owner_name or "Extern"
        try:
            member = self.repository.get_member(self.asset.owner_member_id)
        except RepositoryError:
            return self.asset.owner_member_id or "—"
        return f"{member.member_number or member.member_id} · {member.display_name}"

    def _open_owner_member(self) -> None:
        if self.asset.owner_type == "member" and self.asset.owner_member_id:
            self.on_open_member(self.asset.owner_member_id)

    def _refresh_events(self) -> None:
        self.timeline.delete(*self.timeline.get_children())
        try:
            events = self.repository.get_asset_events(self.asset_id)
        except RepositoryError as exc:
            messagebox.showerror("Chronik beschädigt", str(exc), parent=self)
            return
        for event in reversed(events):
            self.timeline.insert("", "end", values=(_format_timestamp(event), _event_label(event)))

    def _refresh_finance(self) -> None:
        self.asset_claims.delete(*self.asset_claims.get_children())
        linked_claims: list[tuple[str, str, dict]] = []
        for member in self.repository.list_members():
            try:
                data = self.repository.get_contributions(member.member_id)
            except RepositoryError:
                continue
            for claim in data.claims:
                origin = claim.get("origin") or {}
                if isinstance(origin, dict) and str(origin.get("asset_id", "")) == self.asset_id:
                    linked_claims.append((member.member_id, member.display_name, claim))
        for member_id, member_name, claim in linked_claims:
            claim_id = str(claim.get("claim_id", ""))
            self.asset_claims.insert(
                "",
                "end",
                iid=f"{member_id}:{claim_id}",
                values=(
                    str(claim.get("title", "")),
                    str(claim.get("due_date", "")),
                    str(claim.get("amount", "")),
                    member_name,
                ),
            )
        self.finance_summary_var.set(
            "Forderungen für dieses Asset. Negative Betraege dokumentieren Gutschriften/Rueckzahlungen."
        )

    def _save(self) -> None:
        warnings = self.repository.asset_hash_warnings(self.asset_id)
        if warnings:
            self._confirm_integrity_and_then(self._save_confirmed)
            return
        self._save_confirmed()

    def _save_confirmed(self) -> None:
        self.asset.label = self.variables["label"].get().strip()
        self.asset.category = self.variables["category"].get().strip()
        self.asset.inventory_number = self.variables["inventory_number"].get().strip()
        self.asset.serial_number = self.variables["serial_number"].get().strip()
        self.asset.notes = self.notes_text.get("1.0", "end-1c").strip() if self.notes_text else ""
        self.asset.location = self.variables["location"].get().strip()
        self.asset.handed_over_at = self.variables["handed_over_at"].get().strip()
        self.asset.condition = self.variables["condition"].get().strip()
        self.asset.estimated_value = self.variables["estimated_value"].get().strip()
        if not self.asset.current_holder_member_id:
            self.asset.deposit_amount_default = self.variables["deposit_amount_default"].get().strip()
            self.asset.status = _asset_status_key(self.variables["status"].get())
        try:
            self.repository.save_asset(self.asset)
            self.repository.refresh_asset_record_hashes(self.asset_id)
        except RepositoryError as exc:
            messagebox.showerror("Asset konnte nicht gespeichert werden", str(exc), parent=self)
            return
        self.refresh()
        self.on_changed()

    def _confirm_integrity_banner(self) -> None:
        self._confirm_integrity_and_then(self._refresh_hashes_only)

    def _refresh_hashes_only(self) -> None:
        try:
            self.repository.refresh_asset_record_hashes(self.asset_id)
        except RepositoryError as exc:
            messagebox.showerror("Prüfsummen konnten nicht erneuert werden", str(exc), parent=self)
            return
        self.refresh()
        self.on_changed()

    def _confirm_integrity_and_then(self, callback: Callable[[], None]) -> None:
        warnings = self.repository.asset_hash_warnings(self.asset_id)
        if not warnings:
            callback()
            return
        IntegrityWarningDialog(
            self,
            title="Externe Änderungen bestätigen",
            warnings=warnings,
            on_confirm=callback,
        )

    def _add_comment(self) -> None:
        text = self.comment_var.get().strip()
        if not text:
            return
        self.repository.append_asset_event(
            self.asset_id,
            event_type="board_comment",
            summary=text,
            actor_type="user",
            actor_name="Vorstand",
        )
        self.comment_var.set("")
        self._refresh_events()

    def _open_holder_member(self) -> None:
        if self.asset.current_holder_member_id:
            self.on_open_member(self.asset.current_holder_member_id)

    def _create_deposit_claim(self) -> None:
        self._open_claim_dialog(
            preset_title=f"Kaution: {self.asset.label}",
            preset_amount=self.asset.deposit_amount_default or "0.00",
            preset_description=f"Kaution für Asset {self.asset.label}",
            claim_type="asset_deposit",
        )

    def _create_refund_claim(self) -> None:
        amount = self.asset.deposit_amount_default or "0.00"
        if not str(amount).startswith("-"):
            amount = f"-{amount}"
        self._open_claim_dialog(
            preset_title=f"Kautionsrückzahlung: {self.asset.label}",
            preset_amount=amount,
            preset_description=f"Gutschrift / Rückzahlung der Kaution für Asset {self.asset.label}",
            claim_type="asset_refund",
        )

    def _create_loss_claim(self) -> None:
        self._open_claim_dialog(
            preset_title=f"Verlust: {self.asset.label}",
            preset_amount=self.asset.deposit_amount_default or "0.00",
            preset_description=f"Forderung wegen Verlust von Asset {self.asset.label}",
            claim_type="asset_loss",
        )

    def _create_repair_claim(self) -> None:
        self._open_claim_dialog(
            preset_title=f"Reparatur: {self.asset.label}",
            preset_amount="0.00",
            preset_description=f"Reparaturkosten für Asset {self.asset.label}",
            claim_type="asset_repair",
        )

    def _open_claim_dialog(
        self,
        *,
        preset_title: str,
        preset_amount: str,
        preset_description: str,
        claim_type: str,
    ) -> None:
        member_id = self.asset.current_holder_member_id
        if not member_id:
            messagebox.showerror(
                "Forderung nicht möglich",
                "Dieses Asset ist aktuell keinem Mitglied zugeordnet.",
                parent=self,
            )
            return
        AssetClaimDialog(
            self,
            self.repository,
            self.asset_id,
            member_id,
            preset_title=preset_title,
            preset_amount=preset_amount,
            preset_description=preset_description,
            claim_type=claim_type,
            on_created=lambda claim_id: self._claim_created(member_id, claim_id),
        )

    def _claim_created(self, member_id: str, claim_id: str) -> None:
        self.refresh()
        self.on_changed()
        self.on_open_claim(member_id, claim_id)

    def _open_selected_asset_claim(self) -> None:
        selected = self.asset_claims.selection()
        if not selected:
            return
        member_id, claim_id = selected[0].split(":", 1)
        self.on_open_claim(member_id, claim_id)


def _format_timestamp(event: Event) -> str:
    try:
        return datetime.fromisoformat(event.timestamp).strftime("%d.%m.%Y %H:%M")
    except ValueError:
        return event.timestamp[:16]


def _event_label(event: Event) -> str:
    if event.actor_type == "system":
        return f"[AUTO] {event.summary}"
    return event.summary


def _asset_status_key(label: str) -> str:
    for key, value in ASSET_STATUS_LABELS.items():
        if value == label:
            return key
    return label.casefold().strip()
