"""Application login dialog backed by the ``Operatori`` table."""

from __future__ import annotations

import tkinter as tk
from tkinter import messagebox, ttk
from typing import Any

from audit_log import log_session_event
from gestione_aree import AsyncRunner
from locale_text import load_locale_catalog, text as loc_text
from ui_theme import theme_section, theme_value
from user_session import UserSession, create_user_session
from version_info import module_version, versioned_title

__version__ = module_version(__name__)


SQL_LOGIN = """
SELECT TOP (1)
    ID,
    Login,
    Nominativo,
    Privilegio,
    CodiceUnita
FROM dbo.Operatori
WHERE LTRIM(RTRIM(Login)) = :login
  AND LTRIM(RTRIM([Password])) = :password
ORDER BY ID;
"""


def _rows_to_dicts(res: Any) -> list[dict[str, Any]]:
    """Normalize DB responses into a list of row dictionaries."""

    if res is None:
        return []
    if isinstance(res, list):
        return [row for row in res if isinstance(row, dict)]
    if isinstance(res, dict):
        rows = res.get("rows") or res.get("data") or res.get("records") or []
        if rows and isinstance(rows[0], dict):
            return rows
        cols = res.get("columns") or []
        out: list[dict[str, Any]] = []
        for row in rows:
            if isinstance(row, (list, tuple)) and cols:
                out.append({str(cols[i]): row[i] for i in range(min(len(cols), len(row)))})
        return out
    return []


class LoginWindow(tk.Toplevel):
    """Small modal dialog used to authenticate one warehouse operator."""

    def __init__(self, parent: tk.Misc, db_client, *, compact: bool = False):
        super().__init__(parent)
        self.db_client = db_client
        self.compact = bool(compact)
        self.result_session: UserSession | None = None
        self._async = AsyncRunner(self)
        self._theme = theme_section("login_window", {})
        self._locale_catalog = load_locale_catalog()
        self._login_button: ttk.Button | None = None
        self._cancel_button: ttk.Button | None = None
        self._status_var = tk.StringVar(value="")
        self._show_ready_after_id: str | None = None
        self._clear_topmost_after_id: str | None = None

        self.title(versioned_title(loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"), __name__))
        self.geometry("170x148+0+0" if self.compact else str(theme_value(self._theme, "window_geometry", "165x170+0+0")))
        self.resizable(False, False)
        try:
            if parent is not None and parent.winfo_viewable():
                self.transient(parent)
        except Exception:
            pass
        self.protocol("WM_DELETE_WINDOW", self._on_cancel)

        self.login_var = tk.StringVar()
        self.password_var = tk.StringVar()

        self._build_ui()
        self.update_idletasks()
        self.grab_set()
        self.deiconify()
        self.lift()
        self.attributes("-topmost", True)
        self._show_ready_after_id = self.after(50, self._show_ready)

    def _build_ui(self) -> None:
        """Build the compact operator login form."""

        body = ttk.Frame(self, padding=6 if self.compact else 8)
        body.pack(fill="both", expand=True)
        body.columnconfigure(1, weight=0)

        row_offset = 0

        ttk.Label(body, text="User:").grid(row=row_offset, column=0, sticky="w", padx=(0, 4), pady=(2, 3))
        self.login_entry = ttk.Entry(body, textvariable=self.login_var, width=10, font=("Segoe UI", 10 if self.compact else 9))
        self.login_entry.grid(row=row_offset, column=1, sticky="w", pady=(2, 3))

        ttk.Label(body, text="Pwd:").grid(row=row_offset + 1, column=0, sticky="w", padx=(0, 4), pady=(2, 2))
        self.password_entry = ttk.Entry(body, textvariable=self.password_var, width=10, show="*", font=("Segoe UI", 10 if self.compact else 9))
        self.password_entry.grid(row=row_offset + 1, column=1, sticky="w", pady=(2, 2))

        if self.compact:
            actions = ttk.Frame(body)
            actions.grid(row=row_offset + 2, column=0, columnspan=2, sticky="ew", pady=(3, 0))
            self._login_button = ttk.Button(
                actions,
                text="OK",
                command=self._on_login,
            )
            self._login_button.grid(row=0, column=0, sticky="ew")
            self._cancel_button = ttk.Button(
                actions,
                text="Annulla",
                command=self._on_cancel,
            )
            self._cancel_button.grid(row=1, column=0, sticky="ew", pady=(3, 0))
        else:
            self.status_label = ttk.Label(body, textvariable=self._status_var, foreground="#555555")
            self.status_label.grid(row=2, column=0, columnspan=2, sticky="w", pady=(1, 1))

            actions = ttk.Frame(body)
            actions.grid(row=3, column=0, columnspan=2, sticky="w", pady=(3, 0))
            self._login_button = ttk.Button(
                actions,
                text=loc_text("login.button.submit", catalog=self._locale_catalog, default="OK"),
                command=self._on_login,
            )
            self._login_button.grid(row=0, column=0, sticky="ew")
            self._cancel_button = ttk.Button(
                actions,
                text=loc_text("login.button.cancel", catalog=self._locale_catalog, default="Annulla"),
                command=self._on_cancel,
            )
            self._cancel_button.grid(row=1, column=0, sticky="ew", pady=(3, 0))

        for widget in (self.login_entry, self.password_entry, self._login_button, self._cancel_button):
            try:
                widget.configure(takefocus=True)
            except Exception:
                pass

        self.bind("<Return>", lambda _e: self._on_login())
        self.bind("<Escape>", lambda _e: self._on_cancel())
        self.login_var.trace_add("write", lambda *_: self._limit_var(self.login_var, 10))
        self.password_var.trace_add("write", lambda *_: self._limit_var(self.password_var, 10))

    def _limit_var(self, variable: tk.StringVar, max_len: int) -> None:
        value = str(variable.get() or "")
        if len(value) > max_len:
            variable.set(value[:max_len])

    def _set_busy(self, busy: bool, message: str = "") -> None:
        """Enable or disable user interaction during async authentication."""

        state = "disabled" if busy else "normal"
        try:
            self.login_entry.configure(state=state)
            self.password_entry.configure(state=state)
            if self._login_button is not None:
                self._login_button.configure(state=state)
            if self._cancel_button is not None:
                self._cancel_button.configure(state=state)
            self.configure(cursor="watch" if busy else "")
            self._status_var.set(message)
            self.update_idletasks()
        except Exception:
            pass

    def _focus_login(self) -> None:
        """Focus the login entry as soon as the modal becomes visible."""

        try:
            self.login_entry.focus_force()
        except Exception:
            pass

    def _show_ready(self) -> None:
        """Make the login visible and ready even when the bootstrap root is hidden."""

        try:
            self.attributes("-topmost", True)
            self.deiconify()
            self.lift()
            self.focus_force()
            self._focus_login()
        finally:
            try:
                self._clear_topmost_after_id = self.after(250, lambda: self.attributes("-topmost", False))
            except Exception:
                pass

    def _on_login(self) -> None:
        """Validate credentials against the Operatori table."""

        login = str(self.login_var.get() or "").strip()
        password = str(self.password_var.get() or "").strip()
        if not login or not password:
            messagebox.showwarning(
                loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"),
                loc_text("login.msg.missing", catalog=self._locale_catalog, default="Inserisci login e password."),
                parent=self,
            )
            return

        params = {"login": login, "password": password}
        self._set_busy(True, loc_text("login.status.checking", catalog=self._locale_catalog, default="Verifico credenziali..."))

        def _ok(res: Any) -> None:
            rows = _rows_to_dicts(res)
            self._set_busy(False, "")
            if not rows:
                log_session_event(
                    None,
                    action="login.failed",
                    outcome="denied",
                    details={"login": login},
                )
                messagebox.showerror(
                    loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"),
                    loc_text("login.msg.invalid", catalog=self._locale_catalog, default="Credenziali non valide."),
                    parent=self,
                )
                try:
                    self.password_var.set("")
                    self.password_entry.focus_force()
                except Exception:
                    pass
                return

            row = rows[0]
            self.result_session = create_user_session(
                operator_id=int(row.get("ID") or 0),
                login=str(row.get("Login") or login).strip(),
                nominativo=str(row.get("Nominativo") or "").strip(),
                privilegio=int(row["Privilegio"]) if row.get("Privilegio") is not None else None,
                codice_unita=str(row.get("CodiceUnita") or "").strip(),
            )
            log_session_event(
                self.result_session,
                action="login.success",
                outcome="ok",
                details={"display_name": self.result_session.display_name},
            )
            self._close()

        def _err(ex: Exception) -> None:
            self._set_busy(False, "")
            log_session_event(
                None,
                action="login.error",
                outcome="error",
                details={"login": login, "error": str(ex)},
            )
            messagebox.showerror(
                loc_text("login.msg.title", catalog=self._locale_catalog, default="Login"),
                loc_text("login.msg.error", catalog=self._locale_catalog, default="Verifica credenziali fallita:\n{error}").format(error=ex),
                parent=self,
            )

        self._async.run(
            self.db_client.query_json(SQL_LOGIN, params),
            _ok,
            _err,
        )

    def _on_cancel(self) -> None:
        """Abort the login flow and close the modal."""

        log_session_event(None, action="login.cancel", outcome="cancelled")
        self.result_session = None
        self._close()

    def _close(self) -> None:
        """Release the modal grab and destroy the login window."""

        if self._show_ready_after_id is not None:
            try:
                self.after_cancel(self._show_ready_after_id)
            except Exception:
                pass
            self._show_ready_after_id = None
        if self._clear_topmost_after_id is not None:
            try:
                self.after_cancel(self._clear_topmost_after_id)
            except Exception:
                pass
            self._clear_topmost_after_id = None
        try:
            self.grab_release()
        except Exception:
            pass
        try:
            self.destroy()
        except Exception:
            pass


def prompt_login(parent: tk.Misc, db_client) -> UserSession | None:
    """Open the login modal and return the authenticated user session, if any."""

    dialog = LoginWindow(parent, db_client)
    dialog.wait_window()
    return dialog.result_session


def prompt_login_compact(parent: tk.Misc, db_client) -> UserSession | None:
    """Open the barcode-oriented compact login modal."""

    dialog = LoginWindow(parent, db_client, compact=True)
    dialog.wait_window()
    return dialog.result_session
