"""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 gestione_aree import AsyncRunner, BusyOverlay

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.title("Warehouse - Ricerca UDC/Lotto/Codice")
        self.geometry("1100x720")
        self.minsize(900, 560)
        self.resizable(True, True)

        self.db = db_app
        self.session = session
        self._busy = BusyOverlay(self)
        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)
        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)

        ctk.CTkLabel(top, text="UDC:").grid(row=0, column=0, sticky="w")
        self.var_udc = tk.StringVar()
        ctk.CTkEntry(top, textvariable=self.var_udc, width=160).grid(row=0, column=1, sticky="w", padx=(4, 12))

        ctk.CTkLabel(top, text="Lotto:").grid(row=0, column=2, sticky="w")
        self.var_lotto = tk.StringVar()
        ctk.CTkEntry(top, textvariable=self.var_lotto, width=140).grid(row=0, column=3, sticky="w", padx=(4, 12))

        ctk.CTkLabel(top, text="Codice prodotto:").grid(row=0, column=4, sticky="w")
        self.var_codice = tk.StringVar()
        ctk.CTkEntry(top, textvariable=self.var_codice, width=160).grid(row=0, column=5, sticky="w", padx=(4, 12))

        ctk.CTkButton(top, text="Cerca", command=self._do_search).grid(row=0, column=6, sticky="w")
        ctk.CTkButton(top, text="Esporta XLSX", command=self._export_xlsx).grid(row=0, column=7, sticky="e")

        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")
        self._style = ttk.Style(self)
        try:
            self._style.theme_use(self._style.theme_use())
        except Exception:
            pass
        self._style.configure("Search.Treeview", rowheight=22, font=("", 9))
        self._style.configure("Search.Treeview.Heading", font=("", 9, "bold"), background="#F3F4F6")
        self._style.map("Search.Treeview", background=[("selected", "#DCEBFF")])
        self.tree.configure(style="Search.Treeview")
        self.tree.tag_configure("even", background="#FFFFFF")
        self.tree.tag_configure("odd", background="#F7F9FC")
        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 = ("id9999", zebra) if is9999 else (zebra,)
            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("Esporta", "Non ci sono righe da esportare.", parent=self)
            return
        if not _HAS_XLSX:
            messagebox.showerror("Esporta", "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("Esporta", f"File creato:\n{fname}", parent=self)
        except Exception as ex:
            messagebox.showerror("Esporta", f"Errore durante l'esportazione:{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)
        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(
                "Conferma",
                "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.set_sheet_data(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
                    zebra = "even" if idx % 2 == 0 else "odd"
                    try:
                        is9999 = int(idc) == 9999
                    except Exception:
                        is9999 = False
                    tags = ("id9999", zebra) if is9999 else (zebra,)
                    self.tree.insert("", "end", values=(idc, ubi, udc_v, lot_v, cod_v, desc_v), tags=tags)

            if not rows:
                messagebox.showinfo(
                    "Nessun risultato",
                    "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("Errore ricerca", str(ex), parent=self)

        self._async.run(self.db.query_json(SQL_SEARCH, params), _ok, _err, busy=self._busy, message="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)
    return w
