"""Search window for pallets, lots and product codes across the warehouse."""

from __future__ import annotations

import tkinter as tk
from tkinter import filedialog, messagebox, ttk

import customtkinter as ctk

from busy_overlay import InlineBusyOverlay
from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_color, theme_font, theme_section, theme_value
from ui_tables import (
    apply_tksheet_visual_style,
    apply_tksheet_zebra,
    merge_tags,
    style_treeview,
    zebra_tag,
)
from version_info import module_version, versioned_title
from window_placement import place_window_fullsize_below_parent_later
from tooltips import WidgetToolTip, load_tooltip_catalog, tooltip_text

__version__ = module_version(__name__)

try:
    from openpyxl import Workbook
    from openpyxl.styles import Alignment, Font

    _HAS_XLSX = True
except Exception:
    _HAS_XLSX = False

try:
    from tksheet import Sheet
except Exception:
    Sheet = None

SQL_SEARCH = r"""
WITH BASE AS (
    SELECT
        g.IDCella,
        CONCAT(g.BarcodePallet, '') AS UDC,
        c.Corsia,
        c.Colonna,
        c.Fila
    FROM dbo.XMag_GiacenzaPallet AS g
    LEFT JOIN dbo.Celle AS c ON c.ID = g.IDCella
),
JOINED AS (
    SELECT
        b.IDCella,
        b.UDC,
        b.Corsia,
        b.Colonna,
        b.Fila,
        t.Lotto,
        t.Prodotto,
        t.Descrizione
    FROM BASE b
    LEFT JOIN dbo.vXTracciaProdotti AS t
      ON t.Pallet COLLATE Latin1_General_CI_AS = LEFT(b.UDC, 6) COLLATE Latin1_General_CI_AS
)
SELECT
    j.IDCella,
    UPPER(
        CONCAT(
            COALESCE(LTRIM(RTRIM(j.Corsia)), 'NA'), '.',
            COALESCE(LTRIM(RTRIM(CAST(j.Colonna AS varchar(32)))), 'NA'), '.',
            COALESCE(LTRIM(RTRIM(CAST(j.Fila AS varchar(32)))), 'NA')
        )
    ) AS Ubicazione,
    j.UDC,
    j.Lotto,
    j.Prodotto,
    j.Descrizione
FROM JOINED j
WHERE 1=1
  AND ( :udc   IS NULL OR j.UDC COLLATE Latin1_General_CI_AS LIKE CONCAT('%', :udc, '%') )
  AND ( :lotto IS NULL OR j.Lotto COLLATE Latin1_General_CI_AS LIKE CONCAT('%', :lotto, '%') )
  AND ( :codice IS NULL OR j.Prodotto COLLATE Latin1_General_CI_AS LIKE CONCAT('%', :codice, '%') )
ORDER BY
    CASE WHEN j.IDCella = 9999 THEN 1 ELSE 0 END,
    j.Corsia, j.Colonna, j.Fila, j.UDC, j.Lotto, j.Prodotto;
"""


class SearchWindow(ctk.CTkToplevel):
    """Window that searches pallets by barcode, lot or product code."""

    def __init__(self, parent: tk.Widget, db_app, session=None):
        """Initialize widgets and keep a reference to the shared DB client."""
        super().__init__(parent)
        self._theme = theme_section("search_window", {})
        self._locale_catalog = load_locale_catalog()
        self._tooltip_catalog = load_tooltip_catalog()
        self.title(versioned_title(loc_text("search.title", catalog=self._locale_catalog, default="Ricerca UDC/Lotto/Codice"), __name__))
        self.geometry(str(theme_value(self._theme, "window_geometry", "1100x720")))
        minsize = theme_value(self._theme, "window_minsize", [900, 560])
        self.minsize(int(minsize[0]), int(minsize[1]))
        self.resizable(True, True)

        self.db = db_app
        self.session = session
        try:
            self.configure(fg_color=theme_color(self._theme, "window_fg_color", ("#efefef", "#2f2f2f")))
        except Exception:
            pass
        self._busy = InlineBusyOverlay(self, self._theme)
        self._async = AsyncRunner(self)
        self._sort_state: dict[str, bool] = {}

        self._build_ui()

    def _build_ui(self):
        """Create the search form, result tree and scrollbars."""
        self.grid_rowconfigure(0, weight=0)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)

        top = ctk.CTkFrame(
            self,
            fg_color=theme_color(self._theme, "toolbar_frame_fg_color", ("#d7d7d7", "#3b3b3b")),
        )
        top.grid(row=0, column=0, sticky="nsew", padx=8, pady=8)
        for i in range(8):
            top.grid_columnconfigure(i, weight=0)
        top.grid_columnconfigure(7, weight=1)

        label_font = theme_font(self._theme, "toolbar_label_font", ("Segoe UI", 10))
        entry_font = theme_font(self._theme, "entry_font", ("Segoe UI", 10))
        button_font = theme_font(self._theme, "toolbar_button_font", ("Segoe UI", 10, "bold"))

        ctk.CTkLabel(
            top, text=loc_text("search.label.udc", catalog=self._locale_catalog, default="UDC:"), font=label_font
        ).grid(row=0, column=0, sticky="w")
        self.var_udc = tk.StringVar()
        ctk.CTkEntry(top, textvariable=self.var_udc, width=160, font=entry_font).grid(
            row=0, column=1, sticky="w", padx=(4, 12)
        )

        ctk.CTkLabel(
            top, text=loc_text("search.label.lot", catalog=self._locale_catalog, default="Lotto:"), font=label_font
        ).grid(row=0, column=2, sticky="w")
        self.var_lotto = tk.StringVar()
        ctk.CTkEntry(top, textvariable=self.var_lotto, width=140, font=entry_font).grid(
            row=0, column=3, sticky="w", padx=(4, 12)
        )

        ctk.CTkLabel(
            top,
            text=loc_text("search.label.code", catalog=self._locale_catalog, default="Codice prodotto:"),
            font=label_font,
        ).grid(row=0, column=4, sticky="w")
        self.var_codice = tk.StringVar()
        ctk.CTkEntry(top, textvariable=self.var_codice, width=160, font=entry_font).grid(
            row=0, column=5, sticky="w", padx=(4, 12)
        )

        btn_search = ctk.CTkButton(
            top,
            text=loc_text("search.button.search", catalog=self._locale_catalog, default="Cerca"),
            command=self._do_search,
            font=button_font,
        )
        btn_search.grid(row=0, column=6, sticky="w")
        btn_export = ctk.CTkButton(
            top,
            text=loc_text("search.button.export", catalog=self._locale_catalog, default="Esporta XLSX"),
            command=self._export_xlsx,
            font=button_font,
        )
        btn_export.grid(row=0, column=7, sticky="e")
        tip = tooltip_text("launcher.open_search", catalog=self._tooltip_catalog)
        if tip:
            WidgetToolTip(btn_search, tip)

        wrap = ctk.CTkFrame(self)
        wrap.grid(row=1, column=0, sticky="nsew", padx=8, pady=(0, 8))
        wrap.grid_rowconfigure(0, weight=1)
        wrap.grid_columnconfigure(0, weight=1)

        self.use_sheet = False
        cols = ("IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione")
        self.tree = ttk.Treeview(wrap, columns=cols, show="headings")
        headings = {
            "IDCella": ("IDCella", 90, "e"),
            "Ubicazione": ("Ubicazione", 150, "w"),
            "UDC": ("UDC / Barcode", 130, "w"),
            "Lotto": ("Lotto", 130, "w"),
            "Codice": ("Codice prodotto", 150, "w"),
            "Descrizione": ("Descrizione prodotto", 340, "w"),
        }
        for col in cols:
            text, width, anchor = headings[col]
            self.tree.heading(col, text=text)
            self.tree.column(col, width=width, anchor=anchor, stretch=True)
        self._style = style_treeview(
            self.tree,
            style_name="Search.Treeview",
            rowheight=22,
            font=("", 9),
            heading_font=("", 9, "bold"),
        )
        self.tree.tag_configure("id9999", background="#FFECEC", foreground="#B00020")

        sy = ttk.Scrollbar(wrap, orient="vertical", command=self.tree.yview)
        sx = ttk.Scrollbar(wrap, orient="horizontal", command=self.tree.xview)
        self.tree.configure(yscrollcommand=sy.set, xscrollcommand=sx.set)
        self.tree.grid(row=0, column=0, sticky="nsew")
        sy.grid(row=0, column=1, sticky="ns")
        sx.grid(row=1, column=0, sticky="ew")

        self.tree.bind("<Double-1>", self._on_dclick)
        self.tree.bind("<Button-1>", self._maybe_handle_heading_click, add=True)
        self.tree.bind("<Double-1>", self._on_heading_double_click, add=True)

    def _apply_zebra(self):
        """Reapply alternating row colors and special styling for cell 9999."""
        for i, iid in enumerate(self.tree.get_children("")):
            vals = self.tree.item(iid, "values")
            zebra = "even" if i % 2 == 0 else "odd"
            is9999 = False
            if vals:
                try:
                    is9999 = int(vals[0]) == 9999
                except Exception:
                    is9999 = False
            tags = merge_tags(zebra, "id9999" if is9999 else "")
            self.tree.item(iid, tags=tags)

    def _export_xlsx(self):
        """Export the currently visible search results to an Excel file."""
        rows = [self.tree.item(iid, "values") for iid in self.tree.get_children("")]
        if not rows:
            messagebox.showinfo(
                loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
                loc_text("search.msg.export_empty", catalog=self._locale_catalog, default="Non ci sono righe da esportare."),
                parent=self,
            )
            return
        if not _HAS_XLSX:
            messagebox.showerror(
                loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
                loc_text(
                    "search.msg.export_dep",
                    catalog=self._locale_catalog,
                    default="Per l'esportazione serve 'openpyxl' (pip install openpyxl).",
                ),
                parent=self,
            )
            return

        from datetime import datetime

        ts = datetime.now().strftime("%d_%m_%Y_%H-%M")
        default_name = f"esportazione_ricerca_{ts}.xlsx"
        fname = filedialog.asksaveasfilename(
            parent=self,
            title="Esporta in Excel",
            defaultextension=".xlsx",
            filetypes=[("Excel Workbook", "*.xlsx")],
            initialfile=default_name,
        )
        if not fname:
            return
        try:
            wb = Workbook()
            ws = wb.active
            ws.title = "Risultati"
            headers = ("IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione")
            for j, header in enumerate(headers, start=1):
                cell = ws.cell(row=1, column=j, value=header)
                cell.font = Font(bold=True)
                cell.alignment = Alignment(horizontal="center", vertical="center")
            row_idx = 2
            for row in rows:
                for j, value in enumerate(row, start=1):
                    ws.cell(row=row_idx, column=j, value=value)
                row_idx += 1

            widths = {}
            for row in ws.iter_rows(values_only=True):
                for j, value in enumerate(row, start=1):
                    widths[j] = max(widths.get(j, 0), len("" if value is None else str(value)))
            from openpyxl.utils import get_column_letter

            for j, width in widths.items():
                ws.column_dimensions[get_column_letter(j)].width = min(max(width + 2, 10), 60)
            wb.save(fname)
            messagebox.showinfo(
                loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
                f"File creato:\n{fname}",
                parent=self,
            )
        except Exception as ex:
            messagebox.showerror(
                loc_text("search.msg.export_title", catalog=self._locale_catalog, default="Esporta"),
                loc_text("search.msg.export_error", catalog=self._locale_catalog, default="Errore durante l'esportazione:{error}").format(error=ex),
                parent=self,
            )

    def _on_dclick(self, evt):
        """Copy the selected pallet barcode when a result cell is double-clicked."""
        region = self.tree.identify("region", evt.x, evt.y)
        if region != "cell":
            return
        sel = self.tree.focus()
        if not sel:
            return
        vals = self.tree.item(sel, "values")
        if len(vals) >= 3 and vals[2]:
            try:
                self.clipboard_clear()
                self.clipboard_append(vals[2])
            except Exception:
                pass

    def _maybe_handle_heading_click(self, evt):
        """Prevent heading clicks from selecting phantom result rows."""
        region = self.tree.identify("region", evt.x, evt.y)
        if region == "heading":
            return "break"

    def _on_heading_double_click(self, evt):
        """Sort by the double-clicked heading."""
        region = self.tree.identify("region", evt.x, evt.y)
        if region != "heading":
            return
        col_id = self.tree.identify_column(evt.x)
        try:
            idx = int(col_id.replace("#", "")) - 1
        except Exception:
            return "break"
        cols = ("IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione")
        if 0 <= idx < len(cols):
            self._sort_by_column(cols[idx])
        return "break"

    def _sort_key_for_col(self, col: str, val: str):
        """Return a stable sort key for the given column value."""
        if val is None:
            return (1, "")
        s = str(val)
        if col in ("IDCella",):
            try:
                return (0, int(s))
            except Exception:
                return (0, s.lower())
        return (0, s.lower())

    def _sort_by_column(self, col: str):
        """Sort the tree rows in place and update heading arrows."""
        try:
            rev = self._sort_state.get(col, False)
            self._sort_state[col] = not rev

            rows = []
            for iid in self.tree.get_children(""):
                vals = self.tree.item(iid, "values")
                rows.append(
                    {
                        "iid": iid,
                        "IDCella": vals[0] if len(vals) > 0 else None,
                        "Ubicazione": vals[1] if len(vals) > 1 else None,
                        "UDC": vals[2] if len(vals) > 2 else None,
                        "Lotto": vals[3] if len(vals) > 3 else None,
                        "Codice": vals[4] if len(vals) > 4 else None,
                        "Descrizione": vals[5] if len(vals) > 5 else None,
                    }
                )

            rows.sort(key=lambda row: self._sort_key_for_col(col, row.get(col)), reverse=rev)

            def _apply_moves():
                for index, row in enumerate(rows):
                    self.tree.move(row["iid"], "", index)
                titles = {
                    "IDCella": "IDCella",
                    "Ubicazione": "Ubicazione",
                    "UDC": "UDC / Barcode",
                    "Lotto": "Lotto",
                    "Codice": "Codice prodotto",
                    "Descrizione": "Descrizione prodotto",
                }
                for key, base in titles.items():
                    if key == col:
                        arrow = " ▼" if not rev else " ▲"
                        self.tree.heading(key, text=base + arrow)
                    else:
                        self.tree.heading(key, text=base)
                self._apply_zebra()

            self.after_idle(_apply_moves)
        except Exception:
            pass

    def _on_sheet_header_double_click(self, event_dict):
        """Legacy sorter kept for the optional ``tksheet`` backend."""
        try:
            c = event_dict.get("column")
        except Exception:
            return
        if c is None:
            return
        headers = ["IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione"]
        if not (0 <= c < len(headers)):
            return
        colname = headers[c]
        rev = self._sort_state.get(colname, False)
        self._sort_state[colname] = not rev

        data = self.sheet.get_sheet_data(return_copy=True)

        def keyf(row):
            val = row[c] if c < len(row) else None
            if val is None:
                return (1, "")
            s = str(val)
            if colname == "IDCella":
                try:
                    return (0, int(s))
                except Exception:
                    return (0, s.lower())
            return (0, s.lower())

        data.sort(key=keyf, reverse=rev)
        self.sheet.set_sheet_data(data)
        try:
            arrow = " ▼" if not rev else " ▲"
            hdrs = list(headers)
            hdrs[c] = hdrs[c] + arrow
            self.sheet.headers(hdrs)
            apply_tksheet_visual_style(self.sheet)
            apply_tksheet_zebra(self.sheet, len(data))
        except Exception:
            pass

    def _do_search(self):
        """Run the search query using the currently filled filters."""
        udc = (self.var_udc.get() or "").strip()
        lotto = (self.var_lotto.get() or "").strip()
        codice = (self.var_codice.get() or "").strip()

        if not (udc or lotto or codice):
            if not messagebox.askyesno(
                loc_text("search.msg.confirm_title", catalog=self._locale_catalog, default="Conferma"),
                loc_text(
                    "search.msg.confirm_all",
                    catalog=self._locale_catalog,
                    default="Nessun filtro impostato. Vuoi cercare su TUTTO il magazzino?",
                ),
                parent=self,
            ):
                return

        params = {
            "udc": (udc if udc else None),
            "lotto": (lotto if lotto else None),
            "codice": (codice if codice else None),
        }

        def _ok(res):
            rows = res.get("rows", []) if isinstance(res, dict) else []
            if self.use_sheet:
                try:
                    data = []
                    for row in rows:
                        idc, ubi, udc_v, lot_v, cod_v, desc_v = row
                        data.append([idc, ubi, udc_v, lot_v, cod_v, desc_v])
                    self.sheet.headers(["IDCella", "Ubicazione", "UDC", "Lotto", "Codice", "Descrizione"])
                    apply_tksheet_visual_style(self.sheet)
                    self.sheet.set_sheet_data(data)
                    apply_tksheet_zebra(self.sheet, len(data))
                    self.sheet.set_all_cell_sizes_to_text()
                except Exception:
                    self.use_sheet = False
            if not self.use_sheet:
                for iid in self.tree.get_children():
                    self.tree.delete(iid)
                for idx, row in enumerate(rows):
                    idc, ubi, udc_v, lot_v, cod_v, desc_v = row
                    try:
                        is9999 = int(idc) == 9999
                    except Exception:
                        is9999 = False
                    tags = merge_tags(zebra_tag(idx), "id9999" if is9999 else "")
                    self.tree.insert("", "end", values=(idc, ubi, udc_v, lot_v, cod_v, desc_v), tags=tags)

            if not rows:
                messagebox.showinfo(
                    loc_text("search.msg.no_results_title", catalog=self._locale_catalog, default="Nessun risultato"),
                    loc_text(
                        "search.msg.no_results",
                        catalog=self._locale_catalog,
                        default="Nessuna corrispondenza trovata con le chiavi di ricerca inserite.",
                    ),
                    parent=self,
                )
            else:
                self.var_udc.set("")
                self.var_lotto.set("")
                self.var_codice.set("")
            self._busy.hide()

        def _err(ex):
            self._busy.hide()
            messagebox.showerror(
                loc_text("search.msg.error_title", catalog=self._locale_catalog, default="Errore ricerca"),
                str(ex),
                parent=self,
            )

        self._async.run(
            self.db.query_json(SQL_SEARCH, params),
            _ok,
            _err,
            busy=self._busy,
            message=loc_text("search.busy", catalog=self._locale_catalog, default="Cerco..."),
        )


def open_search_window(parent, db_app, session=None):
    """Open a singleton-like search window tied to the launcher instance."""
    key = "_search_window_singleton"
    ex = getattr(parent, key, None)
    if ex and ex.winfo_exists():
        try:
            ex.lift()
            ex.focus_force()
            return ex
        except Exception:
            pass
    w = SearchWindow(parent, db_app, session=session)
    setattr(parent, key, w)
    place_window_fullsize_below_parent_later(parent, w)
    try:
        w.lift()
        w.focus_force()
    except Exception:
        pass
    return w
