"""Runtime helpers for console-less Windows launches."""

from __future__ import annotations

import sys
import threading
import traceback
from datetime import datetime
from pathlib import Path
import tempfile
from typing import Callable, TypeVar

from version_info import module_version

__version__ = module_version(__name__)

BASE_DIR = Path(__file__).resolve().parent
FATAL_LOG = BASE_DIR / "warehouse_fatal.log"
TEMP_DIR = Path(tempfile.gettempdir())
_STDIO_HANDLES = []
_EXCEPTION_LOGGING_CONFIGURED: set[str] = set()
T = TypeVar("T")


def _open_log_file(path: Path):
    """Open a log path, falling back to the user temp directory when needed."""

    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        return path.open("a", encoding="utf-8", buffering=1)
    except Exception:
        fallback = TEMP_DIR / path.name
        fallback.parent.mkdir(parents=True, exist_ok=True)
        return fallback.open("a", encoding="utf-8", buffering=1)


def ensure_stdio(app_name: str) -> None:
    """Give ``pythonw`` a real stdout/stderr target before loggers are imported."""

    stamp = datetime.now().strftime("%Y%m%d")
    if sys.stdout is None:
        handle = _open_log_file(BASE_DIR / f"{app_name}_stdout_{stamp}.log")
        _STDIO_HANDLES.append(handle)
        sys.stdout = handle
    if sys.stderr is None:
        handle = _open_log_file(BASE_DIR / f"{app_name}_stderr_{stamp}.log")
        _STDIO_HANDLES.append(handle)
        sys.stderr = handle


def log_fatal(app_name: str, exc: BaseException) -> None:
    """Write one startup/runtime crash to a persistent log file."""

    with _open_log_file(FATAL_LOG) as handle:
        handle.write("\n" + "=" * 80 + "\n")
        handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {app_name}\n")
        handle.write("".join(traceback.format_exception(type(exc), exc, exc.__traceback__)))


def log_exception(app_name: str, exc: BaseException, *, context: str = "") -> None:
    """Write a handled or callback exception to the persistent fatal log."""

    with _open_log_file(FATAL_LOG) as handle:
        handle.write("\n" + "=" * 80 + "\n")
        handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {app_name}")
        if context:
            handle.write(f" | {context}")
        handle.write("\n")
        handle.write("".join(traceback.format_exception(type(exc), exc, exc.__traceback__)))


def configure_exception_logging(app_name: str, root=None, loop=None) -> None:
    """Install process, Tk and asyncio exception hooks for console-less apps."""

    if app_name in _EXCEPTION_LOGGING_CONFIGURED:
        return
    _EXCEPTION_LOGGING_CONFIGURED.add(app_name)

    previous_excepthook = sys.excepthook

    def _sys_excepthook(exc_type, exc, tb):
        log_exception(app_name, exc, context="sys.excepthook")
        if previous_excepthook:
            previous_excepthook(exc_type, exc, tb)

    sys.excepthook = _sys_excepthook

    if hasattr(threading, "excepthook"):
        previous_threading_excepthook = threading.excepthook

        def _threading_excepthook(args):
            log_exception(app_name, args.exc_value, context=f"thread={getattr(args.thread, 'name', '')}")
            previous_threading_excepthook(args)

        threading.excepthook = _threading_excepthook

    if root is not None:
        def _tk_exception(exc_type, exc, tb):
            log_exception(app_name, exc, context="tkinter callback")
            try:
                import tkinter.messagebox as messagebox

                messagebox.showerror(
                    app_name,
                    "Errore applicativo registrato nei log.\n\n"
                    "Se l'operazione era in corso, ripeterla o avvisare il responsabile.",
                    parent=root,
                )
            except Exception:
                pass

        try:
            root.report_callback_exception = _tk_exception
        except Exception:
            pass

    if loop is not None:
        previous_loop_handler = None
        try:
            previous_loop_handler = loop.get_exception_handler()
        except Exception:
            previous_loop_handler = None

        def _loop_exception_handler(active_loop, context):
            exc = context.get("exception")
            if exc is not None:
                log_exception(app_name, exc, context=f"asyncio: {context.get('message', '')}")
            else:
                log_runtime_event(app_name, f"ASYNCIO {context!r}")
            if previous_loop_handler is not None:
                previous_loop_handler(active_loop, context)

        try:
            loop.set_exception_handler(_loop_exception_handler)
        except Exception:
            pass


def log_runtime_event(app_name: str, message: str) -> None:
    """Write a lightweight launch/shutdown trace for console-less starts."""

    safe_name = "".join(ch if ch.isalnum() or ch in ("_", "-") else "_" for ch in app_name.lower())
    path = BASE_DIR / f"{safe_name}_launch.log"
    with _open_log_file(path) as handle:
        handle.write(f"{datetime.now():%Y-%m-%d %H:%M:%S} | {message}\n")


def show_fatal_message(app_name: str, exc: BaseException) -> None:
    """Show a best-effort message box for console-less launches."""

    try:
        import tkinter as tk
        from tkinter import messagebox

        root = tk.Tk()
        root.withdraw()
        messagebox.showerror(
            app_name,
            "Avvio non riuscito.\n\n"
            f"Controlla i log in:\n{BASE_DIR}\n\n"
            f"Se non ci sono, controlla anche:\n{TEMP_DIR}\n\n"
            f"{exc}",
            parent=root,
        )
        root.destroy()
    except Exception:
        pass


def run_with_fatal_log(app_name: str, func: Callable[[], T]) -> T:
    """Run an app entry point and persist otherwise invisible ``pythonw`` crashes."""

    try:
        log_runtime_event(
            app_name,
            f"START exe={sys.executable!r} version={sys.version.split()[0]} cwd={Path.cwd()} base={BASE_DIR} argv={sys.argv!r}",
        )
        result = func()
        log_runtime_event(app_name, f"RETURN result={result!r}")
        return result
    except BaseException as exc:
        log_runtime_event(app_name, f"EXCEPTION type={type(exc).__name__} value={exc!r}")
        log_fatal(app_name, exc)
        show_fatal_message(app_name, exc)
        raise
