#!/usr/bin/env python3
"""
mcp_nostr_explorer — punkt wejścia.

Uruchomienie:
  python main.py              # port 5001 — twardy fail jeśli zajęty
  python main.py 5005         # konkretny port — twardy fail jeśli zajęty

Deterministyczny port: nie szukamy wolnego, bo w przeglądarce wpisujesz
konkretny URL i wolimy padnąć z czytelnym błędem niż wystartować na
losowym porcie i zostawić cię z białą stroną.
"""

import logging
import os
import sys
import threading

# Pozwól uruchamiać main.py zarówno z wnętrza katalogu jak i jako moduł
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import uvicorn  # noqa: E402

from mcp_nostr_explorer.server import create_app  # noqa: E402

LOG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "explorer.log")

_root = logging.getLogger()
_root.setLevel(logging.DEBUG)

_fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")

_console = logging.StreamHandler()
_console.setLevel(logging.INFO)
_console.setFormatter(_fmt)
_root.addHandler(_console)

_file = logging.FileHandler(LOG_PATH, mode="a", encoding="utf-8")
_file.setLevel(logging.DEBUG)
_file.setFormatter(_fmt)
_root.addHandler(_file)

logger = logging.getLogger("mcp_nostr_explorer.main")
logger.info(f"--- start, log file: {LOG_PATH} ---")

DEFAULT_PORT = 5001
HOST = "127.0.0.1"

# Twardy budżet czasowy na graceful shutdown. Po przekroczeniu wątek-watchdog
# wywołuje os._exit(130) — uvicorn potrafi wisieć na nieukończonych WS / long-
# pollach mimo `timeout_graceful_shutdown`, a user oczekuje że Ctrl-C ZAWSZE
# kończy proces. Drugi Ctrl-C w tym oknie ubija od razu, bez czekania.
FORCE_EXIT_AFTER_S = 3.0


class ForceExitServer(uvicorn.Server):
    """Uvicorn server z watchdogiem: pierwszy Ctrl-C startuje graceful
    shutdown PLUS uzbraja timer, który po `FORCE_EXIT_AFTER_S` woła
    `os._exit(130)` niezależnie od stanu pętli (otwarte WS, ingest threads,
    asyncio tasks). Drugi Ctrl-C omija timer i kończy natychmiast."""

    _force_armed = False

    def handle_exit(self, sig, frame):
        if self._force_armed:
            logger.warning("Drugi Ctrl-C — force exit teraz.")
            os._exit(130)
        self._force_armed = True
        logger.info(
            f"Ctrl-C odebrany. Graceful shutdown ≤{FORCE_EXIT_AFTER_S}s, "
            f"potem twardy os._exit. Ctrl-C ponownie = natychmiast."
        )
        watchdog = threading.Thread(
            target=self._force_exit_watchdog,
            name="force-exit-watchdog",
            daemon=True,
        )
        watchdog.start()
        super().handle_exit(sig, frame)

    @staticmethod
    def _force_exit_watchdog() -> None:
        # threading.Event() z timeoutem zamiast time.sleep — daemon=True
        # i tak ubija wątek razem z procesem, ale to czytelniej wyraża intencję.
        threading.Event().wait(FORCE_EXIT_AFTER_S)
        logger.warning(
            f"Graceful shutdown nie zdążył w {FORCE_EXIT_AFTER_S}s — os._exit(130)."
        )
        os._exit(130)


def main() -> None:
    port = int(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PORT

    app = create_app()
    logger.info(f"mcp_nostr_explorer nasłuchuje na http://{HOST}:{port}/")
    config = uvicorn.Config(
        app,
        host=HOST,
        port=port,
        log_level="info",
        # WS-y trzymają połączenia otwarte — bez tego graceful wisi w
        # nieskończoność. Trzymamy mały budżet, watchdog w ForceExitServer
        # i tak twardo zakończy proces po `FORCE_EXIT_AFTER_S`.
        timeout_graceful_shutdown=int(FORCE_EXIT_AFTER_S),
    )
    ForceExitServer(config).run()


if __name__ == "__main__":
    main()
