import atexit
import threading
import traceback
from loguru import logger

from ..__version__ import __version__
from ..utilities.log_utils import (
    config_logger,
    flush_log_queue,
    start_log_queue_processor,
    stop_log_queue_processor,
)
from .app_factory import create_app
from .server_config import load_server_config


def _install_thread_excepthook() -> None:
    """Install a global hook that loudly logs uncaught exceptions on any
    thread — including daemon threads — so silent crashes in the queue
    processor, APScheduler jobs, or the post-login background thread
    surface in logs instead of leaving the app wedged with no signal.

    Respects a previously-installed hook if any (chains to it).
    """
    previous = threading.excepthook

    def _hook(args: threading.ExceptHookArgs) -> None:
        # Don't try to log for SystemExit-in-thread; that is intentional.
        if issubclass(args.exc_type, SystemExit):
            return
        try:
            tb = "".join(
                traceback.format_exception(
                    args.exc_type, args.exc_value, args.exc_traceback
                )
            )
            thread_name = (
                args.thread.name if args.thread is not None else "unknown"
            )
            logger.error(
                f"Uncaught exception on thread {thread_name!r}: "
                f"{args.exc_type.__name__}: {args.exc_value}\n{tb}"
            )
        except Exception:
            pass  # noqa: silent-exception — last-ditch; the excepthook itself must never crash the interpreter
        finally:
            # Chain to the previous hook (usually threading's default).
            try:
                previous(args)
            except Exception:
                pass  # noqa: silent-exception — previous hook failing must not turn our hook into a crash vector

    threading.excepthook = _hook


@logger.catch
def main():
    """
    Entry point for the web application when run as a command.
    This function is needed for the package's entry point to work properly.
    """
    # Install the excepthook before any other threads are spawned so
    # uncaught exceptions in daemon threads (queue processor, APScheduler
    # jobs, post-login background thread) surface in logs instead of
    # dying silently.
    _install_thread_excepthook()

    # Configure logging with milestone level
    config = load_server_config()
    config_logger("ldr_web", debug=config["debug"])
    logger.info(f"Starting Local Deep Research v{__version__}")

    # Create the Flask app and SocketIO instance
    app, socket_service = create_app()

    # Start the background log-queue processor. With no ``before_request``
    # handler pulling from the queue, this daemon is the only drain path
    # during normal operation; a final drain runs at atexit.
    daemon_started = False
    try:
        start_log_queue_processor(app)
        daemon_started = True
    except Exception:
        logger.exception("Failed to start log queue processor")

    # Get web server settings from environment variables (LDR_WEB_HOST, etc.)
    # These require a server restart to take effect
    host = config["host"]
    port = config["port"]
    debug = config["debug"]
    use_https = config["use_https"]

    if use_https:
        # For development, use self-signed certificate
        logger.info("Starting server with HTTPS (self-signed certificate)")
        # Note: SocketIOService doesn't support SSL context directly
        # For production, use a reverse proxy like nginx for HTTPS
        logger.warning(
            "HTTPS requested but not supported directly. Use a reverse proxy for HTTPS."
        )

    # Start periodic cleanup of idle database connections
    # Guard against Flask debug reloader spawning duplicate schedulers
    import os

    cleanup_scheduler = None
    if not debug or os.environ.get("WERKZEUG_RUN_MAIN") == "true":
        from .auth.connection_cleanup import start_connection_cleanup_scheduler
        from .auth.session_manager import session_manager
        from ..database.encrypted_db import db_manager

        try:
            cleanup_scheduler = start_connection_cleanup_scheduler(
                session_manager, db_manager
            )
        except Exception:
            logger.warning(
                "Failed to start cleanup scheduler; idle connections will not be auto-closed",
            )

    def shutdown_scheduler():
        if (
            hasattr(app, "background_job_scheduler")
            and app.background_job_scheduler
        ):
            try:
                app.background_job_scheduler.stop()
                logger.info("News subscription scheduler stopped gracefully")
            except Exception:
                logger.exception("Error stopping scheduler")

    def shutdown_databases():
        try:
            from ..database.encrypted_db import db_manager

            db_manager.close_all_databases()
            logger.info("Database connections closed gracefully")
        except Exception:
            logger.exception("Error closing database connections")

    def flush_logs_on_exit():
        """Drain remaining queued logs after the daemon has stopped."""
        try:
            # Use a minimal Flask context here rather than the main app so
            # the flush still works if the main app is already torn down.
            from flask import Flask

            exit_app = Flask(__name__)
            with exit_app.app_context():
                flush_log_queue()
        except Exception:
            logger.exception("Failed to flush logs on exit")

    # atexit runs LIFO, so register in reverse of desired execution order.
    # Desired execution:
    #   1. stop_log_queue_processor — daemon releases the queue
    #   2. flush_logs_on_exit       — drain whatever the daemon missed
    #   3. shutdown_scheduler + cleanup_scheduler — stop other workers
    #   4. shutdown_databases       — close engines last
    atexit.register(shutdown_databases)
    atexit.register(shutdown_scheduler)
    if cleanup_scheduler is not None:
        atexit.register(lambda: cleanup_scheduler.shutdown(wait=False))
    atexit.register(flush_logs_on_exit)
    if daemon_started:
        atexit.register(stop_log_queue_processor)

    # Use the SocketIOService's run method which properly runs the socketio server
    socket_service.run(host=host, port=port, debug=debug)


if __name__ == "__main__":
    main()
