from __future__ import annotations

import secrets
import tkinter as tk
from copy import deepcopy
from datetime import datetime
from tkinter import messagebox, ttk

from app_core.paths import DB_PATH
from app_core.server_api import fetch_app_setting, save_app_setting
from app_core.ui_helpers import configure_striped_treeview
from database import default_supplier_payload, delete_supplier, list_suppliers, normalize_supplier_row, save_supplier

SUPPLIER_APP_SETTING_KEY = "supplier_records"
SUPPLIER_COLUMNS = (
    "supplier_code",
    "supplier_name",
    "supplier_type",
    "contact_person",
    "phone",
    "email",
    "status",
    "updated_at",
)
SUPPLIER_STATUS_OPTIONS = ["Active", "Inactive", "Draft", "Blocked"]
SUPPLIER_TYPE_OPTIONS = ["Fabric", "Trim", "Label", "Packaging", "Artwork", "Service", "Logistics", "Other"]


def _normalize_supplier_row_list(payload: object) -> list[dict]:
    if not isinstance(payload, list):
        return []
    normalized_rows: list[dict] = []
    for item in payload:
        normalized = normalize_supplier_row(item)
        if isinstance(normalized, dict):
            normalized_rows.append(normalized)
    return normalized_rows


def load_supplier_rows_from_source(self) -> list[dict]:
    if self.should_use_server_data_source():
        try:
            payload = fetch_app_setting(self.server_settings, SUPPLIER_APP_SETTING_KEY, [])
            rows = _normalize_supplier_row_list(payload)
            if rows:
                return rows
        except Exception:
            pass
    return list_suppliers(DB_PATH)


def save_supplier_row_to_source(self, row: dict) -> dict:
    normalized = normalize_supplier_row(row)
    if normalized is None:
        return {}
    if self.should_use_server_data_source():
        try:
            payload = fetch_app_setting(self.server_settings, SUPPLIER_APP_SETTING_KEY, [])
            current_rows = _normalize_supplier_row_list(payload)
        except Exception:
            current_rows = []
        supplier_id = str(normalized.get("supplier_id", "")).strip()
        merged_rows: list[dict] = []
        replaced = False
        for existing_row in current_rows:
            existing_id = str(existing_row.get("supplier_id", "")).strip()
            if supplier_id and existing_id == supplier_id:
                merged_rows.append(normalized)
                replaced = True
            else:
                merged_rows.append(existing_row)
        if not replaced:
            merged_rows.append(normalized)
        payload = save_app_setting(self.server_settings, SUPPLIER_APP_SETTING_KEY, merged_rows)
        saved_rows = _normalize_supplier_row_list(payload)
        matched = next((item for item in saved_rows if str(item.get("supplier_id", "")).strip() == supplier_id), normalized)
        return matched
    saved = save_supplier(DB_PATH, normalized)
    return dict(saved) if isinstance(saved, dict) else {}


def delete_supplier_row_from_source(self, supplier_id: str) -> None:
    normalized_supplier_id = str(supplier_id or "").strip()
    if not normalized_supplier_id:
        return
    if self.should_use_server_data_source():
        try:
            payload = fetch_app_setting(self.server_settings, SUPPLIER_APP_SETTING_KEY, [])
            current_rows = _normalize_supplier_row_list(payload)
        except Exception:
            current_rows = []
        remaining_rows = [
            row for row in current_rows
            if str(row.get("supplier_id", "")).strip() != normalized_supplier_id
        ]
        save_app_setting(self.server_settings, SUPPLIER_APP_SETTING_KEY, remaining_rows)
        return
    delete_supplier(DB_PATH, normalized_supplier_id)


def get_selected_supplier_row_id(self) -> str:
    tree = getattr(self, "supplier_tree", None)
    if tree is None:
        return ""
    selection = tree.selection()
    return str(selection[0]).strip() if selection else ""


def get_selected_supplier_row(self) -> dict | None:
    supplier_id = self.get_selected_supplier_row_id()
    if not supplier_id:
        return None
    for row in getattr(self, "supplier_rows", []):
        if str(row.get("supplier_id", "")).strip() == supplier_id:
            return dict(row)
    return None


def refresh_supplier_tree(self) -> None:
    tree = getattr(self, "supplier_tree", None)
    if tree is None:
        return
    selected_supplier_id = self.get_selected_supplier_row_id()
    tree.delete(*tree.get_children())
    for index, row in enumerate(getattr(self, "filtered_supplier_rows", []), start=1):
        supplier_id = str(row.get("supplier_id", "")).strip()
        if not supplier_id:
            continue
        values = tuple(self.display_value(row.get(key)) for key in SUPPLIER_COLUMNS)
        tree.insert("", "end", iid=supplier_id, values=values, tags=("odd_row" if index % 2 == 1 else "even_row",))
    if selected_supplier_id and tree.exists(selected_supplier_id):
        tree.selection_set(selected_supplier_id)
        tree.focus(selected_supplier_id)
    self.on_supplier_row_selected()
    count = len(getattr(self, "filtered_supplier_rows", []))
    total = len(getattr(self, "supplier_rows", []))
    status_var = getattr(self, "supplier_status_var", None)
    if isinstance(status_var, tk.StringVar):
        status_var.set(f"Suppliers: {count}/{total}")


def apply_supplier_filter(self) -> None:
    query = str(getattr(self, "supplier_search_committed_value", getattr(self, "supplier_search_var", tk.StringVar(value="")).get()) or "").strip().lower()
    rows = list(getattr(self, "supplier_rows", []))
    if query:
        filtered: list[dict] = []
        for row in rows:
            haystack = " ".join(
                [
                    self.display_value(row.get("supplier_code")),
                    self.display_value(row.get("supplier_name")),
                    self.display_value(row.get("short_name")),
                    self.display_value(row.get("supplier_type")),
                    self.display_value(row.get("contact_person")),
                    self.display_value(row.get("phone")),
                    self.display_value(row.get("email")),
                    self.display_value(row.get("country")),
                    self.display_value(row.get("city")),
                    self.display_value(row.get("status")),
                ]
            ).lower()
            if query in haystack:
                filtered.append(row)
        rows = filtered
    rows.sort(key=lambda item: (str(item.get("supplier_name", "")).lower(), str(item.get("supplier_code", "")).lower()))
    self.filtered_supplier_rows = rows
    self.refresh_supplier_tree()


def commit_supplier_search_query(self, value: object | None = None, *, apply_filter: bool = True) -> None:
    if value is None:
        search_var = getattr(self, "supplier_search_var", tk.StringVar(value=""))
        committed_value = search_var.get()
    else:
        committed_value = value
    self.supplier_search_committed_value = str(committed_value or "").strip()
    if apply_filter:
        self.apply_supplier_filter()


def on_supplier_search_submit(self, _event: object | None = None) -> str:
    self.commit_supplier_search_query()
    return "break"


def refresh_supplier_list_window(self) -> None:
    self.supplier_rows = [row for row in self.load_supplier_rows_from_source() if isinstance(row, dict)]
    self.apply_supplier_filter()


def open_supplier_detail_window(self, row: dict | None = None) -> None:
    source_row = deepcopy(row) if isinstance(row, dict) else default_supplier_payload()
    is_new = row is None
    required_action = "action.bom.supplier_add" if is_new else "action.bom.supplier_edit"
    if not self.is_action_allowed(required_action):
        return

    dialog = tk.Toplevel(self.root)
    dialog.title("New Supplier" if is_new else "Edit Supplier")
    dialog.resizable(True, True)
    dialog.geometry("920x640")
    dialog.minsize(820, 560)

    frame = ttk.Frame(dialog, padding=16)
    frame.pack(fill="both", expand=True)
    frame.columnconfigure(0, weight=1)
    frame.rowconfigure(1, weight=1)

    header = ttk.Frame(frame)
    header.grid(row=0, column=0, sticky="ew")
    ttk.Label(header, text=dialog.title(), style="Section.TLabel").pack(side="left")

    content = ttk.Frame(frame)
    content.grid(row=1, column=0, sticky="nsew", pady=(12, 0))
    content.columnconfigure(0, weight=1)
    content.columnconfigure(1, weight=1)

    vars_map = {key: tk.StringVar(value=str(source_row.get(key, "")).strip()) for key in default_supplier_payload()}
    text_widgets: dict[str, tk.Text] = {}

    def add_labeled_entry(
        host: ttk.Frame,
        row_index: int,
        column_index: int,
        label: str,
        key: str,
        *,
        columnspan: int = 1,
    ) -> ttk.Entry:
        field = ttk.Frame(host)
        field.grid(row=row_index, column=column_index, columnspan=columnspan, sticky="ew", padx=8, pady=(0, 12))
        field.columnconfigure(0, weight=1)
        ttk.Label(field, text=label).grid(row=0, column=0, sticky="w", pady=(0, 4))
        entry = ttk.Entry(field, textvariable=vars_map[key])
        entry.grid(row=1, column=0, sticky="ew")
        return entry

    def add_labeled_combo(host: ttk.Frame, row_index: int, column_index: int, label: str, key: str, values: list[str]) -> ttk.Combobox:
        field = ttk.Frame(host)
        field.grid(row=row_index, column=column_index, sticky="ew", padx=8, pady=(0, 12))
        field.columnconfigure(0, weight=1)
        ttk.Label(field, text=label).grid(row=0, column=0, sticky="w", pady=(0, 4))
        combo = ttk.Combobox(field, textvariable=vars_map[key], values=values, state="readonly")
        combo.grid(row=1, column=0, sticky="ew")
        return combo

    def add_labeled_text(host: ttk.Frame, row_index: int, label: str, key: str, *, height: int = 5) -> tk.Text:
        field = ttk.Frame(host)
        field.grid(row=row_index, column=0, columnspan=2, sticky="nsew", padx=8, pady=(0, 12))
        field.columnconfigure(0, weight=1)
        field.rowconfigure(1, weight=1)
        ttk.Label(field, text=label).grid(row=0, column=0, sticky="w", pady=(0, 4))
        text_widget = tk.Text(field, height=height, wrap="word", font=("Segoe UI", 10))
        text_widget.grid(row=1, column=0, sticky="nsew")
        existing = str(source_row.get(key, "")).strip()
        if existing:
            text_widget.insert("1.0", existing)
        text_widgets[key] = text_widget
        return text_widget

    for column_index in range(2):
        content.columnconfigure(column_index, weight=1)
    content.rowconfigure(6, weight=1)

    add_labeled_entry(content, 0, 0, "Supplier Code", "supplier_code")
    add_labeled_entry(content, 0, 1, "Supplier Name", "supplier_name")
    add_labeled_entry(content, 1, 0, "Short Name", "short_name")
    add_labeled_combo(content, 1, 1, "Supplier Type", "supplier_type", SUPPLIER_TYPE_OPTIONS)
    add_labeled_combo(content, 2, 0, "Status", "status", SUPPLIER_STATUS_OPTIONS)
    add_labeled_entry(content, 2, 1, "Contact Person", "contact_person")
    add_labeled_entry(content, 3, 0, "Phone", "phone")
    add_labeled_entry(content, 3, 1, "Email", "email")
    add_labeled_entry(content, 4, 0, "Country", "country")
    add_labeled_entry(content, 4, 1, "City", "city")
    add_labeled_entry(content, 5, 0, "Tax Code", "tax_code")
    add_labeled_entry(content, 5, 1, "Payment Term", "payment_term")
    add_labeled_entry(content, 6, 0, "Currency", "currency")
    add_labeled_entry(content, 6, 1, "Address", "address_line")
    add_labeled_text(content, 7, "Note", "note", height=8)

    actions = ttk.Frame(frame)
    actions.grid(row=2, column=0, sticky="e", pady=(12, 0))

    def save_action() -> None:
        if not self.is_action_allowed(required_action):
            return
        payload = default_supplier_payload()
        for key in payload:
            payload[key] = vars_map[key].get().strip()
        for key, widget in text_widgets.items():
            payload[key] = widget.get("1.0", "end-1c").strip()
        if not payload["supplier_code"]:
            messagebox.showwarning(self.t("warning_title"), "Supplier Code is required.")
            return
        if not payload["supplier_name"]:
            messagebox.showwarning(self.t("warning_title"), "Supplier Name is required.")
            return
        existing = normalize_supplier_row(source_row) or default_supplier_payload()
        payload["supplier_id"] = payload["supplier_id"] or str(existing.get("supplier_id", "")).strip() or secrets.token_hex(16)
        payload["created_at"] = str(existing.get("created_at", "")).strip() or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        try:
            saved = self.save_supplier_row_to_source(payload)
        except Exception as exc:
            messagebox.showerror(self.t("warning_title"), str(exc))
            return
        if not saved:
            messagebox.showerror(self.t("warning_title"), "Could not save supplier.")
            return
        self.refresh_supplier_list_window()
        saved_supplier_id = str(saved.get("supplier_id", "")).strip()
        if hasattr(self, "supplier_tree") and saved_supplier_id and self.supplier_tree.exists(saved_supplier_id):
            self.supplier_tree.selection_set(saved_supplier_id)
            self.supplier_tree.focus(saved_supplier_id)
        self.set_status(f"Saved supplier {saved.get('supplier_code', '')}.")
        dialog.destroy()

    ttk.Button(actions, text=self.t("close"), command=dialog.destroy).pack(side="right")
    save_button = ttk.Button(actions, text="Save", command=save_action)
    save_button.pack(side="right", padx=(0, 8))
    self.register_permission_widget(required_action, save_button)


def open_new_supplier_dialog(self) -> None:
    self.open_supplier_detail_window(None)


def edit_selected_supplier_row(self) -> None:
    if not self.is_action_allowed("action.bom.supplier_edit"):
        return
    row = self.get_selected_supplier_row()
    if row is None:
        messagebox.showwarning(self.t("warning_title"), "Please select one supplier first.")
        return
    self.open_supplier_detail_window(row)


def delete_selected_supplier_row(self) -> None:
    if not self.is_action_allowed("action.bom.supplier_delete"):
        return
    row = self.get_selected_supplier_row()
    if row is None:
        messagebox.showwarning(self.t("warning_title"), "Please select one supplier first.")
        return
    supplier_name = str(row.get("supplier_name", "")).strip() or str(row.get("supplier_code", "")).strip()
    confirmed = messagebox.askyesno(self.t("warning_title"), f"Delete supplier '{supplier_name}'?")
    if not confirmed:
        return
    self.delete_supplier_row_from_source(str(row.get("supplier_id", "")).strip())
    self.refresh_supplier_list_window()
    self.set_status(f"Deleted supplier {supplier_name}.")


def on_supplier_row_selected(self, _event: object | None = None) -> None:
    preview_vars = getattr(self, "supplier_preview_vars", None)
    if not isinstance(preview_vars, dict):
        return
    row = self.get_selected_supplier_row()
    if row is None:
        for variable in preview_vars.values():
            if isinstance(variable, tk.StringVar):
                variable.set("")
        return
    for key, variable in preview_vars.items():
        if not isinstance(variable, tk.StringVar):
            continue
        variable.set(self.display_value(row.get(key)))


def on_supplier_row_double_click(self, _event: object | None = None) -> None:
    self.edit_selected_supplier_row()


def open_bom_supplier_list_window(self) -> None:
    if not self.is_action_allowed("action.bom.supplier_list"):
        return
    existing = getattr(self, "supplier_list_window", None)
    if existing is not None:
        try:
            if existing.winfo_exists():
                existing.deiconify()
                existing.lift()
                existing.focus_force()
                self.refresh_supplier_list_window()
                return
        except Exception:
            pass

    dialog = tk.Toplevel(self.root)
    dialog.title("Supplier List")
    dialog.resizable(True, True)
    dialog.geometry("1120x700")
    dialog.minsize(980, 620)
    self.supplier_list_window = dialog
    dialog.protocol("WM_DELETE_WINDOW", lambda: _close_supplier_window(self))

    frame = ttk.Frame(dialog, padding=16)
    frame.pack(fill="both", expand=True)
    frame.columnconfigure(0, weight=1)
    frame.rowconfigure(3, weight=1)
    frame.rowconfigure(4, weight=0)

    header = ttk.Frame(frame)
    header.grid(row=0, column=0, sticky="ew")
    ttk.Label(header, text="Supplier List", style="Section.TLabel").pack(side="left")

    toolbar = ttk.Frame(frame)
    toolbar.grid(row=1, column=0, sticky="ew", pady=(12, 0))
    add_button = ttk.Button(toolbar, text="Add", command=self.open_new_supplier_dialog)
    add_button.pack(side="left")
    edit_button = ttk.Button(toolbar, text="Edit", command=self.edit_selected_supplier_row)
    edit_button.pack(side="left", padx=(8, 0))
    delete_button = ttk.Button(toolbar, text="Delete", command=self.delete_selected_supplier_row)
    delete_button.pack(side="left", padx=(8, 0))
    self.register_permission_widget("action.bom.supplier_add", add_button)
    self.register_permission_widget("action.bom.supplier_edit", edit_button)
    self.register_permission_widget("action.bom.supplier_delete", delete_button)

    filter_row = ttk.Frame(frame)
    filter_row.grid(row=2, column=0, sticky="ew", pady=(12, 8))
    filter_row.columnconfigure(1, weight=1)
    ttk.Label(filter_row, text="Search").grid(row=0, column=0, sticky="w", padx=(0, 6))
    self.supplier_search_var = tk.StringVar(value="")
    supplier_search_entry = ttk.Entry(filter_row, textvariable=self.supplier_search_var, width=42)
    supplier_search_entry.grid(row=0, column=1, sticky="ew")
    supplier_search_entry.bind("<Return>", self.on_supplier_search_submit)
    supplier_search_entry.bind("<KP_Enter>", self.on_supplier_search_submit)

    table_frame = ttk.Frame(frame)
    table_frame.grid(row=3, column=0, sticky="nsew")
    table_frame.columnconfigure(0, weight=1)
    table_frame.rowconfigure(0, weight=1)

    self.supplier_tree = ttk.Treeview(table_frame, columns=SUPPLIER_COLUMNS, show="headings", selectmode="browse", style="Costing.Treeview", height=14)
    heading_labels = {
        "supplier_code": "Supplier Code",
        "supplier_name": "Supplier Name",
        "supplier_type": "Type",
        "contact_person": "Contact Person",
        "phone": "Phone",
        "email": "Email",
        "status": self.t("status"),
        "updated_at": self.t("revised_time_column"),
    }
    for column_key, width in {
        "supplier_code": 120,
        "supplier_name": 220,
        "supplier_type": 120,
        "contact_person": 150,
        "phone": 120,
        "email": 220,
        "status": 100,
        "updated_at": 130,
    }.items():
        stretch = column_key in {"supplier_name", "email"}
        self.supplier_tree.column(column_key, width=width, anchor="w", stretch=stretch)
        self.supplier_tree.heading(column_key, text=heading_labels.get(column_key, column_key))
    configure_striped_treeview(self.supplier_tree)
    scroll_y = ttk.Scrollbar(table_frame, orient="vertical", command=self.supplier_tree.yview)
    scroll_x = ttk.Scrollbar(table_frame, orient="horizontal", command=self.supplier_tree.xview)
    self.supplier_tree.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
    self.supplier_tree.grid(row=0, column=0, sticky="nsew")
    scroll_y.grid(row=0, column=1, sticky="ns")
    scroll_x.grid(row=1, column=0, sticky="ew")
    self.supplier_tree.bind("<<TreeviewSelect>>", self.on_supplier_row_selected)
    self.supplier_tree.bind("<Double-1>", self.on_supplier_row_double_click)

    preview_frame = ttk.LabelFrame(frame, text="Basic Information", padding=12)
    preview_frame.grid(row=4, column=0, sticky="ew", pady=(12, 0))
    for column_index in range(4):
        preview_frame.columnconfigure(column_index, weight=1)
    self.supplier_preview_vars = {
        "supplier_code": tk.StringVar(value=""),
        "supplier_name": tk.StringVar(value=""),
        "supplier_type": tk.StringVar(value=""),
        "status": tk.StringVar(value=""),
        "contact_person": tk.StringVar(value=""),
        "phone": tk.StringVar(value=""),
        "email": tk.StringVar(value=""),
        "country": tk.StringVar(value=""),
        "city": tk.StringVar(value=""),
        "tax_code": tk.StringVar(value=""),
        "payment_term": tk.StringVar(value=""),
        "currency": tk.StringVar(value=""),
        "address_line": tk.StringVar(value=""),
        "note": tk.StringVar(value=""),
    }

    def add_preview_field(host: ttk.Frame, row_index: int, column_index: int, label: str, key: str, *, columnspan: int = 1) -> None:
        field = ttk.Frame(host)
        field.grid(row=row_index, column=column_index, columnspan=columnspan, sticky="ew", padx=8, pady=(0, 10))
        field.columnconfigure(0, weight=1)
        ttk.Label(field, text=label).grid(row=0, column=0, sticky="w", pady=(0, 2))
        ttk.Label(field, textvariable=self.supplier_preview_vars[key], relief="sunken", anchor="w").grid(row=1, column=0, sticky="ew")

    add_preview_field(preview_frame, 0, 0, "Supplier Code", "supplier_code")
    add_preview_field(preview_frame, 0, 1, "Supplier Name", "supplier_name", columnspan=2)
    add_preview_field(preview_frame, 0, 3, "Status", "status")
    add_preview_field(preview_frame, 1, 0, "Type", "supplier_type")
    add_preview_field(preview_frame, 1, 1, "Contact Person", "contact_person")
    add_preview_field(preview_frame, 1, 2, "Phone", "phone")
    add_preview_field(preview_frame, 1, 3, "Email", "email")
    add_preview_field(preview_frame, 2, 0, "Country", "country")
    add_preview_field(preview_frame, 2, 1, "City", "city")
    add_preview_field(preview_frame, 2, 2, "Tax Code", "tax_code")
    add_preview_field(preview_frame, 2, 3, "Payment Term", "payment_term")
    add_preview_field(preview_frame, 3, 0, "Currency", "currency")
    add_preview_field(preview_frame, 3, 1, "Address", "address_line", columnspan=3)
    add_preview_field(preview_frame, 4, 0, "Note", "note", columnspan=4)

    self.supplier_status_var = tk.StringVar(value="")
    status_label = ttk.Label(
        frame,
        textvariable=self.supplier_status_var,
        relief="sunken",
        anchor="w",
        justify="left",
        padding=8,
        wraplength=920,
    )
    status_label.grid(row=5, column=0, sticky="ew", pady=(12, 0))

    self.refresh_supplier_list_window()
    self.apply_permission_state()
    self.center_dialog(dialog, min_width=1120, min_height=700)
    supplier_search_entry.focus_set()


def _close_supplier_window(self) -> None:
    dialog = getattr(self, "supplier_list_window", None)
    if dialog is None:
        return
    try:
        dialog.destroy()
    except Exception:
        pass
    self.supplier_list_window = None


def bind_supplier_module(app_cls: type) -> None:
    method_map = {
        "load_supplier_rows_from_source": load_supplier_rows_from_source,
        "save_supplier_row_to_source": save_supplier_row_to_source,
        "delete_supplier_row_from_source": delete_supplier_row_from_source,
        "get_selected_supplier_row_id": get_selected_supplier_row_id,
        "get_selected_supplier_row": get_selected_supplier_row,
        "refresh_supplier_tree": refresh_supplier_tree,
        "commit_supplier_search_query": commit_supplier_search_query,
        "on_supplier_search_submit": on_supplier_search_submit,
        "apply_supplier_filter": apply_supplier_filter,
        "refresh_supplier_list_window": refresh_supplier_list_window,
        "open_supplier_detail_window": open_supplier_detail_window,
        "open_new_supplier_dialog": open_new_supplier_dialog,
        "edit_selected_supplier_row": edit_selected_supplier_row,
        "delete_selected_supplier_row": delete_selected_supplier_row,
        "on_supplier_row_selected": on_supplier_row_selected,
        "on_supplier_row_double_click": on_supplier_row_double_click,
        "open_bom_supplier_list_window": open_bom_supplier_list_window,
    }
    for method_name, function in method_map.items():
        setattr(app_cls, method_name, function)
