"""Lightweight standalone barcode client for the warehouse WMS."""

from __future__ import annotations

import asyncio
import sys
import tkinter as tk
from concurrent.futures import Future
from tkinter import messagebox, ttk
from typing import Callable

from runtime_support import configure_exception_logging, ensure_stdio, log_exception, run_with_fatal_log

ensure_stdio("warehouse_barcode")

from async_loop_singleton import get_global_loop, stop_global_loop
from async_msssql_query import AsyncMSSQLClient
from barcode_repository import BarcodeRepository
from barcode_service import BarcodeActionResult, BarcodeService, BarcodeViewState
from db_config import build_dsn_from_config, ensure_db_config
from login_window import prompt_login_compact
from user_session import create_user_session
from version_info import module_version, versioned_title

__version__ = module_version(__name__)
BYPASS_LOGIN = True
BYPASS_LOGIN_USER = {
    "operator_id": 4,
    "login": "MAG1",
    "nominativo": "MAG1",
    "privilegio": 3,
    "codice_unita": "U1",
}


def _build_bypass_session():
    """Create the temporary MAG1 session used while field testing the barcode."""

    return create_user_session(
        operator_id=int(BYPASS_LOGIN_USER["operator_id"]),
        login=str(BYPASS_LOGIN_USER["login"]),
        nominativo=str(BYPASS_LOGIN_USER["nominativo"]),
        privilegio=int(BYPASS_LOGIN_USER["privilegio"]),
        codice_unita=str(BYPASS_LOGIN_USER["codice_unita"]),
    )


class BarcodeClientApp:
    """Single-window Tk barcode client modeled after the C# legacy form."""

    PALLET_BARCODE_LENGTH = 6
    NON_SCAFFALATA_BARCODE = "9001000"
    SHIPPED_BARCODE = "9000000"
    BARCODE_MAX_WIDTH = 320
    BARCODE_MAX_HEIGHT = 400
    DESKTOP_THRESHOLD_WIDTH = 1024
    DESKTOP_THRESHOLD_HEIGHT = 768
    DESKTOP_WINDOW_WIDTH = 465
    DESKTOP_WINDOW_HEIGHT = 531
    def __init__(self, root: tk.Tk, db_client: AsyncMSSQLClient, session, loop: asyncio.AbstractEventLoop):
        self.root = root
        self.db_client = db_client
        self.session = session
        self.loop = loop
        self.repository = BarcodeRepository(db_client)
        self.service = BarcodeService(self.repository, session.operator_id)
        self._pending: Future | None = None
        self._auto_advance_id: str | None = None
        self._pallet_auto_focus_id: str | None = None
        self._paused_queue_id: int | None = None
        self._paused_priority_state: BarcodeViewState | None = None
        self._pallet_user_input_serial = 0
        self._priority_loaded_input_serial = 0
        self._priority_expected_pallet = ""
        self._status_colors = {
            "red": "#f4cccc",
            "green": "#d9ead3",
            "yellow": "#e2f0cb",
        }
        self._is_barcode_desktop = False

        self.queue_var = tk.StringVar(value="")
        self.info1_var = tk.StringVar(value="")
        self.info2_var = tk.StringVar(value="")
        self.info3_var = tk.StringVar(value="")
        self.info4_var = tk.StringVar(value="")
        self.destination_var = tk.StringVar(value="")
        self.scanned_var = tk.StringVar(value="")

        self._build_ui()
        self._apply_state(self.service.state)
        self._bind_keys()
        self.root.protocol("WM_DELETE_WINDOW", self._shutdown)
        self.root.after(80, self._finalize_window_placement)
        self.root.after(250, self._finalize_window_placement)

    def _build_ui(self) -> None:
        self.root.title(versioned_title("WMS", __name__))
        self.root.configure(bg="#f1f1f1")
        self._apply_responsive_geometry()

        if self._is_barcode_desktop:
            field_font = ("Segoe UI", 9)
            entry_font = ("Segoe UI", 11)
            band_font = ("Segoe UI", 9, "bold")
            info_font = ("Segoe UI", 9)
            queue_font = ("Segoe UI", 8)
            pad_x = 5
            pad_y = 4
            band_wrap = 190
            info_wrap = 194
            button_pad_y = 1
        else:
            field_font = ("Segoe UI", 12)
            entry_font = ("Segoe UI", 15)
            band_font = ("Segoe UI", 12, "bold")
            info_font = ("Segoe UI", 12)
            queue_font = ("Segoe UI", 10)
            pad_x = 16
            pad_y = 12
            band_wrap = 400
            info_wrap = 408
            button_pad_y = 4

        wrap = tk.Frame(self.root, bg="#f1f1f1", padx=pad_x, pady=pad_y)
        wrap.pack(fill="both", expand=True)
        wrap.rowconfigure(3, weight=1)
        wrap.columnconfigure(0, weight=1)

        header = tk.Frame(wrap, bg="#f1f1f1")
        header.grid(row=0, column=0, sticky="ew", pady=(0, 4 if self._is_barcode_desktop else 10))
        tk.Label(
            header,
            textvariable=self.queue_var,
            bg="#f1f1f1",
            fg="#333333",
            font=queue_font,
            anchor="e",
        ).pack(side="right")

        form = tk.Frame(wrap, bg="#f1f1f1")
        form.grid(row=1, column=0, sticky="ew")
        self._add_compact_field(form, 0, "Pallet", self.scanned_var, scanned=True, label_font=field_font, entry_font=entry_font)
        self._add_compact_field(form, 1, "Cella", self.destination_var, scanned=False, label_font=field_font, entry_font=entry_font)

        self.status_band = tk.Label(
            wrap,
            textvariable=self.info1_var,
            bg=self._status_colors["red"],
            fg="#1f2937",
            font=band_font,
            anchor="w",
            justify="left",
            padx=6,
            pady=3 if self._is_barcode_desktop else 6,
            wraplength=band_wrap,
        )
        self.status_band.grid(row=2, column=0, sticky="ew", pady=(8 if self._is_barcode_desktop else 14, 4))

        info_box = tk.Frame(wrap, bg="#f1f1f1")
        info_box.grid(row=3, column=0, sticky="nsew")
        info_box.columnconfigure(0, weight=1)
        for row_index in range(3):
            info_box.rowconfigure(row_index, weight=1)
        self.info2_label = tk.Label(
            info_box,
            textvariable=self.info2_var,
            bg="#f1f1f1",
            fg="#222222",
            font=info_font,
            anchor="w",
            justify="left",
            wraplength=info_wrap,
        )
        self.info2_label.grid(row=0, column=0, sticky="ew", pady=(2, 0))
        self.info3_label = tk.Label(
            info_box,
            textvariable=self.info3_var,
            bg="#f1f1f1",
            fg="#222222",
            font=info_font,
            anchor="w",
            justify="left",
            wraplength=info_wrap,
        )
        self.info3_label.grid(row=1, column=0, sticky="ew", pady=(2, 0))
        self.info4_label = tk.Label(
            info_box,
            textvariable=self.info4_var,
            bg="#f1f1f1",
            fg="#222222",
            font=info_font,
            anchor="w",
            justify="left",
            wraplength=info_wrap,
        )
        self.info4_label.grid(row=2, column=0, sticky="ew", pady=(2, 4 if self._is_barcode_desktop else 10))

        buttons = tk.Frame(wrap, bg="#f1f1f1")
        buttons.grid(row=4, column=0, sticky="ew", pady=(4, 0))
        buttons.columnconfigure(0, weight=1)
        buttons.columnconfigure(1, weight=1)

        self.btn_f1 = ttk.Button(buttons, text="[F1] H Priority", command=lambda: self._start_queue(1))
        self.btn_f1.grid(row=0, column=0, padx=(0, 4), pady=(0, button_pad_y), sticky="ew")
        self.btn_submit = ttk.Button(buttons, text="[F3] Carica", command=self._on_f3)
        self.btn_submit.grid(row=0, column=1, padx=(4, 0), pady=(0, button_pad_y), sticky="ew")
        self.btn_f2 = ttk.Button(buttons, text="[F2] L Priority", command=lambda: self._start_queue(0))
        self.btn_f2.grid(row=1, column=0, padx=(0, 4), sticky="ew")
        self.btn_unload = ttk.Button(buttons, text="[F4] Scarica", command=self._begin_manual_unload)
        self.btn_unload.grid(row=1, column=1, padx=(4, 0), sticky="ew")

        self.busy_cover = tk.Frame(self.root, bg="#d9d9d9")
        panel = ttk.Frame(self.busy_cover, padding=16)
        panel.place(relx=0.5, rely=0.5, anchor="center")
        self.busy_label = ttk.Label(panel, text="Attendere...", font=("Segoe UI", 11, "bold"))
        self.busy_label.pack(pady=(0, 8))
        self.busy_bar = ttk.Progressbar(panel, mode="indeterminate", length=260)
        self.busy_bar.pack()
    def _add_compact_field(
        self,
        parent: tk.Frame,
        row: int,
        label: str,
        variable: tk.StringVar,
        *,
        scanned: bool,
        label_font,
        entry_font,
    ) -> None:
        tk.Label(
            parent,
            text=label,
            bg="#f1f1f1",
            fg="#1f1f1f",
            font=label_font,
            anchor="w",
        ).grid(row=row, column=0, sticky="w", padx=(0, 8), pady=4 if self._is_barcode_desktop else 6)
        entry = ttk.Entry(parent, textvariable=variable, font=entry_font, width=8)
        entry.grid(row=row, column=1, sticky="ew", pady=4 if self._is_barcode_desktop else 6, ipady=1 if self._is_barcode_desktop else 4)
        parent.columnconfigure(1, weight=1)
        if scanned:
            self.pallet_entry = entry
            self.pallet_entry.bind("<KeyPress>", self._on_pallet_keypress, add="+")
            self.scanned_var.trace_add("write", lambda *_: self._on_scanned_var_changed())
        else:
            self.destination_entry = entry
            self.destination_var.trace_add("write", lambda *_: self._on_destination_var_changed())

    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 _on_pallet_keypress(self, _event=None) -> None:
        self._pallet_user_input_serial += 1

    def _on_scanned_var_changed(self) -> None:
        self._limit_var(self.scanned_var, 8)
        if self._pallet_auto_focus_id is not None:
            try:
                self.root.after_cancel(self._pallet_auto_focus_id)
            except Exception:
                pass
            self._pallet_auto_focus_id = None
        pallet = str(self.scanned_var.get() or "").strip()
        if len(pallet) < self.PALLET_BARCODE_LENGTH:
            return
        self._pallet_auto_focus_id = self.root.after(80, self._auto_focus_destination_after_scan)

    def _on_destination_var_changed(self) -> None:
        self._limit_var(self.destination_var, 8)
        self._refresh_action_labels()

    def _auto_focus_destination_after_scan(self) -> None:
        self._pallet_auto_focus_id = None
        pallet = str(self.scanned_var.get() or "").strip()
        if not pallet:
            return
        if self._pending is not None and not self._pending.done():
            return
        if getattr(self.service.state, "mode", "") == "confirm":
            return
        if getattr(self.service.state, "mode", "") in ("priority_high", "priority_low"):
            expected = str(getattr(self.service.state, "expected_pallet", "") or "").strip()
            if expected and pallet == expected and self._is_fresh_priority_scan(expected):
                self._submit()
            return
        if bool(getattr(self.service.state, "destination_readonly", False)):
            return
        self._focus_destination_input()

    def _apply_responsive_geometry(self) -> None:
        """Adapt the window size to barcode-sized or desktop-sized screens."""

        self.root.update_idletasks()
        screen_width = max(1, int(self.root.winfo_screenwidth() or 0))
        screen_height = max(1, int(self.root.winfo_screenheight() or 0))

        is_barcode_desktop = (
            screen_width <= self.BARCODE_MAX_WIDTH or screen_height <= self.BARCODE_MAX_HEIGHT
        )
        self._is_barcode_desktop = is_barcode_desktop

        if is_barcode_desktop:
            width = screen_width
            height = screen_height
            x = 0
            y = 0
            self.root.minsize(max(220, width), max(300, height))
        else:
            width = min(self.DESKTOP_WINDOW_WIDTH, screen_width)
            height = min(self.DESKTOP_WINDOW_HEIGHT, screen_height)
            x = 0
            y = 0
            self.root.minsize(420, 500)

        if (
            screen_width >= self.DESKTOP_THRESHOLD_WIDTH
            and screen_height >= self.DESKTOP_THRESHOLD_HEIGHT
            and not is_barcode_desktop
        ):
            width = min(self.DESKTOP_WINDOW_WIDTH, screen_width)
            height = min(self.DESKTOP_WINDOW_HEIGHT, screen_height)
            x = 0
            y = 0

        self.root.geometry(f"{width}x{height}+{x}+{y}")

    def _finalize_window_placement(self) -> None:
        """Let Windows finish frame sizing, then snap the barcode window in place."""

        try:
            if self._is_barcode_desktop:
                self.root.state("zoomed")
            else:
                self.root.geometry("+0+0")
            self.root.update_idletasks()
        except Exception:
            pass

    def _bind_keys(self) -> None:
        self.root.bind("<F1>", lambda _e: self._start_queue(1))
        self.root.bind("<F2>", lambda _e: self._start_queue(0))
        self.root.bind("<F3>", self._on_f3_key)
        self.root.bind("<F4>", self._on_unload_key)
        self.root.bind("<Escape>", self._on_escape_key)
        self.pallet_entry.bind("<Return>", self._on_pallet_enter)
        self.destination_entry.bind("<Return>", self._on_destination_enter)

    def _set_busy(self, busy: bool, message: str = "") -> None:
        state = "disabled" if busy else "normal"
        for button in (self.btn_f1, self.btn_f2, self.btn_unload, self.btn_submit):
            try:
                button.configure(state=state)
            except Exception:
                pass
        try:
            self.pallet_entry.configure(state=state)
        except Exception:
            pass
        try:
            if str(self.destination_entry.cget("state")) != "readonly" or busy:
                self.destination_entry.configure(state=state)
        except Exception:
            pass
        if busy:
            self.busy_label.configure(text=message or "Attendere...")
            self.busy_cover.place(relx=0, rely=0, relwidth=1, relheight=1)
            self.busy_bar.start(10)
        else:
            self.busy_bar.stop()
            self.busy_cover.place_forget()

    def _is_priority_pause(self) -> bool:
        return str(self.queue_var.get() or "") == "Pausa PL"

    def _apply_state(self, state: BarcodeViewState) -> None:
        if self._auto_advance_id is not None:
            try:
                self.root.after_cancel(self._auto_advance_id)
            except Exception:
                pass
            self._auto_advance_id = None
        if self._pallet_auto_focus_id is not None:
            try:
                self.root.after_cancel(self._pallet_auto_focus_id)
            except Exception:
                pass
            self._pallet_auto_focus_id = None
        pause_context = self._paused_queue_id is not None and state.mode not in ("priority_high", "priority_low")
        effective_queue_label = "Pausa PL" if pause_context else state.queue_label
        self.queue_var.set(effective_queue_label)
        self.destination_var.set(state.destination_barcode)
        self.scanned_var.set(state.scanned_pallet)
        self.info1_var.set(self._status_text_with_wait_hint(state))
        self.info2_var.set(state.document)
        self.info3_var.set(state.customer)
        self.info4_var.set(state.expected_pallet)
        self.status_band.configure(bg=state.status_color or self._status_colors["red"])

        if state.mode in ("priority_high", "priority_low"):
            self._priority_loaded_input_serial = self._pallet_user_input_serial
            self._priority_expected_pallet = str(state.expected_pallet or "").strip()
        else:
            self._priority_expected_pallet = ""

        try:
            if str(effective_queue_label or "") != "Pausa PL":
                self._paused_queue_id = None
            resumable_queue = self._resumable_queue_from_state(state)
            if state.mode in ("priority_high", "priority_low") or resumable_queue is not None:
                self.btn_submit.configure(text="[F3] Pausa PL")
            else:
                self.btn_submit.configure(text="[F3] Carica")
            self._refresh_action_labels(state)
            queue_buttons_state = "disabled" if str(effective_queue_label or "") == "Pausa PL" else "normal"
            self.btn_f1.configure(state=queue_buttons_state)
            self.btn_f2.configure(state=queue_buttons_state)
        except Exception:
            pass

        destination_readonly = False if pause_context else bool(getattr(state, "destination_readonly", False))
        try:
            self.destination_entry.configure(state="normal")
            if destination_readonly:
                self.destination_entry.configure(state="readonly")
        except Exception:
            pass

        if state.mode == "confirm":
            next_queue = self._resumable_queue_from_state(state)
            if next_queue is not None:
                self._auto_advance_id = self.root.after(
                    int(getattr(state, "auto_advance_delay_ms", 0) or 0),
                    lambda q=next_queue: self._start_queue(q),
                )

        if bool(getattr(state, "focus_destination", False)):
            self.root.after(20, self._focus_destination_input)
        else:
            self.root.after(20, self._focus_primary_input)

    def _refresh_action_labels(self, state: BarcodeViewState | None = None) -> None:
        current_state = state or self.service.state
        try:
            if current_state.mode in ("priority_high", "priority_low"):
                self.btn_unload.configure(text="[F4] Salta UDC")
                return
            destination = str(self.destination_var.get() or "").strip()
            if destination == self.NON_SCAFFALATA_BARCODE:
                self.btn_unload.configure(text="[F4] Non scaff.")
            elif destination:
                self.btn_unload.configure(text="[F4] Dissocia")
            else:
                self.btn_unload.configure(text="[F4] Scarica")
        except Exception:
            pass

    def _focus_primary_input(self) -> None:
        try:
            self.pallet_entry.focus_force()
            self.pallet_entry.selection_range(0, "end")
        except Exception:
            pass

    def _status_text_with_wait_hint(self, state: BarcodeViewState) -> str:
        text = str(getattr(state, "status_text", "") or "")
        delay_ms = int(getattr(state, "auto_advance_delay_ms", 0) or 0)
        if delay_ms <= 0:
            return text
        seconds = max(1, round(delay_ms / 1000))
        hint = f"Attendi {seconds} secondi..."
        if hint.lower() in text.lower():
            return text
        return f"{text} - {hint}" if text else hint

    def _focus_destination_input(self) -> None:
        try:
            self.destination_entry.configure(state="normal")
        except Exception:
            pass
        try:
            self.destination_entry.focus_force()
            self.destination_entry.selection_range(0, "end")
        except Exception:
            pass

    def _is_fresh_priority_scan(self, expected: str) -> bool:
        mode = str(getattr(self.service.state, "mode", "") or "")
        if mode not in ("priority_high", "priority_low"):
            return False
        if str(expected or "").strip() != self._priority_expected_pallet:
            return False
        return self._pallet_user_input_serial > self._priority_loaded_input_serial

    def _on_pallet_enter(self, _event=None) -> str:
        pallet = str(self.scanned_var.get() or "").strip()
        destination = str(self.destination_var.get() or "").strip()
        if not pallet:
            return "break"
        if destination in (self.NON_SCAFFALATA_BARCODE, self.SHIPPED_BARCODE):
            if bool(getattr(self.service.state, "destination_readonly", False)):
                mode = str(getattr(self.service.state, "mode", "") or "")
                if mode in ("priority_high", "priority_low"):
                    expected = str(getattr(self.service.state, "expected_pallet", "") or "").strip()
                    if expected and pallet == expected and self._is_fresh_priority_scan(expected):
                        self._submit()
                    else:
                        self._focus_primary_input()
                else:
                    self._submit()
            else:
                self._focus_destination_input()
            return "break"
        self.destination_var.set("")
        self._focus_destination_input()
        return "break"

    def _on_destination_enter(self, _event=None) -> str:
        pallet = str(self.scanned_var.get() or "").strip()
        destination = str(self.destination_var.get() or "").strip()
        if pallet and destination:
            self._submit()
        else:
            self._focus_primary_input()
        return "break"

    def _on_unload_key(self, _event=None) -> str:
        self._begin_manual_unload()
        return "break"

    def _on_escape_key(self, _event=None) -> str:
        if self._is_priority_pause() and self._paused_queue_id is not None:
            if self._paused_priority_state is not None:
                state = self.service.resume_priority_state(self._paused_priority_state)
                self._paused_priority_state = None
                self._paused_queue_id = None
                self._apply_state(state)
                return "break"
            self._start_queue(self._paused_queue_id, allow_during_pause=True)
        return "break"

    def _on_f3_key(self, _event=None) -> str:
        self._on_f3()
        return "break"

    def _on_f3(self) -> None:
        mode = getattr(self.service.state, "mode", "")
        if mode == "priority_high":
            self._paused_queue_id = 1
            self._paused_priority_state = self.service.state
            self._apply_state(self.service.begin_priority_pause(1))
            return
        if mode == "priority_low":
            self._paused_queue_id = 0
            self._paused_priority_state = self.service.state
            self._apply_state(self.service.begin_priority_pause(0))
            return
        resumable_queue = self._resumable_queue_from_state(self.service.state)
        if resumable_queue is not None:
            self._paused_queue_id = resumable_queue
            self._paused_priority_state = None
            self._apply_state(self.service.begin_priority_pause(resumable_queue))
            return
        self._submit()

    def _begin_manual_unload(self) -> None:
        pallet = str(self.scanned_var.get() or "").strip()
        destination = str(self.destination_var.get() or "").strip()
        mode = getattr(self.service.state, "mode", "")
        if mode in ("priority_high", "priority_low"):
            self._skip_current_picking_pallet()
            return
        if pallet:
            if destination == self.NON_SCAFFALATA_BARCODE:
                self._submit()
            else:
                self._submit_unload_with_source_check()
            return
        if self._is_priority_pause():
            return
        self._apply_state(self.service.begin_manual_unload())

    def _start_queue(self, id_stato: int, *, allow_during_pause: bool = False) -> None:
        if self._is_priority_pause() and not allow_during_pause:
            return
        self._run_async(
            lambda: self.service.start_priority_queue(id_stato),
            busy_message="In preparazione...",
        )

    def _submit(self) -> None:
        self._run_async(
            lambda: self.service.submit(
                scanned_pallet=self.scanned_var.get(),
                destination_barcode=self.destination_var.get(),
            ),
            busy_message="In esecuzione...",
        )

    def _submit_unload_with_source_check(self) -> None:
        self._run_async(
            lambda: self.service.submit_unload_with_source_check(
                scanned_pallet=self.scanned_var.get(),
                source_barcode=self.destination_var.get(),
            ),
            busy_message="In esecuzione...",
        )

    def _skip_current_picking_pallet(self) -> None:
        self._run_async(
            lambda: self.service.skip_current_picking_pallet(),
            busy_message="Registro salto UDC...",
        )

    def _run_async(self, coro_factory: Callable[[], object], busy_message: str) -> None:
        if self._pending is not None and not self._pending.done():
            return
        if self._auto_advance_id is not None:
            try:
                self.root.after_cancel(self._auto_advance_id)
            except Exception:
                pass
            self._auto_advance_id = None
        self._set_busy(True, busy_message)
        self._pending = asyncio.run_coroutine_threadsafe(coro_factory(), self.loop)
        self.root.after(40, self._poll_future)

    def _queue_id_from_label(self, queue_label: str) -> int | None:
        text = str(queue_label or "")
        if text.startswith("Alta priorita'"):
            return 1
        if text.startswith("Bassa priorita'"):
            return 0
        return None

    def _resumable_queue_from_state(self, state: BarcodeViewState) -> int | None:
        delay_ms = int(getattr(state, "auto_advance_delay_ms", 0) or 0)
        if delay_ms <= 0:
            return None
        return self._queue_id_from_label(str(getattr(state, "queue_label", "") or ""))

    def _poll_future(self) -> None:
        if self._pending is None:
            self._set_busy(False)
            return
        if not self._pending.done():
            self.root.after(40, self._poll_future)
            return

        future = self._pending
        self._pending = None
        self._set_busy(False)
        try:
            result = future.result()
        except Exception as exc:
            log_exception("Barcode WMS", exc, context="barcode async operation")
            self._set_busy(True, "Verifico esito...")
            self._pending = asyncio.run_coroutine_threadsafe(
                self.service.reconcile_after_submit_exception(
                    scanned_pallet=self.scanned_var.get(),
                    destination_barcode=self.destination_var.get(),
                ),
                self.loop,
            )
            self.root.after(40, lambda err=exc: self._poll_recovery_future(err))
            return

        if isinstance(result, BarcodeActionResult):
            self._apply_state(result.state)
            if result.message and not result.ok:
                self.root.bell()
        elif isinstance(result, BarcodeViewState):
            self._apply_state(result)

    def _show_transaction_error(self) -> None:
        messagebox.showerror(
            "Barcode WMS",
            "Operazione non completata.\n\n"
            "Ripeti la lettura o avvisa il responsabile.\n"
            "Il dettaglio tecnico e' stato scritto nel log.",
            parent=self.root,
        )

    def _poll_recovery_future(self, original_exception: Exception) -> None:
        if self._pending is None:
            self._set_busy(False)
            self._show_transaction_error()
            return
        if not self._pending.done():
            self.root.after(40, lambda err=original_exception: self._poll_recovery_future(err))
            return

        future = self._pending
        self._pending = None
        self._set_busy(False)
        try:
            result = future.result()
        except Exception as exc:
            log_exception("Barcode WMS", exc, context=f"barcode recovery after {original_exception!r}")
            current = self.service.state
            current.status_text = "Transazione non completata, ripeti l'operazione."
            current.status_color = "#f4cccc"
            self._apply_state(current)
            self._show_transaction_error()
            return

        if isinstance(result, BarcodeActionResult):
            self._apply_state(result.state)
            if not result.ok:
                self.root.bell()
                self._show_transaction_error()
        elif isinstance(result, BarcodeViewState):
            self._apply_state(result)

    def _shutdown(self) -> None:
        try:
            if self._pending is not None and not self._pending.done():
                return
        except Exception:
            pass
        try:
            fut = asyncio.run_coroutine_threadsafe(self.db_client.dispose(), self.loop)
            fut.result(timeout=2)
        except Exception:
            pass
        try:
            self.root.destroy()
        finally:
            try:
                stop_global_loop()
            except Exception:
                pass


def main() -> int:
    loop = get_global_loop()
    bootstrap = tk.Tk()
    bootstrap.withdraw()
    configure_exception_logging("Barcode WMS", root=bootstrap, loop=loop)

    config = ensure_db_config(loop, parent=bootstrap)
    if not config:
        bootstrap.destroy()
        stop_global_loop()
        return 1

    db_client = AsyncMSSQLClient(build_dsn_from_config(config))
    session = _build_bypass_session() if BYPASS_LOGIN else prompt_login_compact(bootstrap, db_client)
    if session is None:
        try:
            fut = asyncio.run_coroutine_threadsafe(db_client.dispose(), loop)
            fut.result(timeout=2)
        except Exception:
            pass
        bootstrap.destroy()
        stop_global_loop()
        return 1

    app = BarcodeClientApp(bootstrap, db_client, session, loop)
    bootstrap.deiconify()
    bootstrap.lift()
    bootstrap.focus_force()
    app._focus_primary_input()
    bootstrap.mainloop()
    return 0


if __name__ == "__main__":
    raise SystemExit(run_with_fatal_log("Barcode WMS", main))
