"""Standalone entrypoint для Telegram support-bridge воркера (#tgsupport).

Зачем отдельный процесс/контейнер: `getUpdates` long-polling держит открытый
HTTP-запрос к Telegram до 30с за раз в бесконечном цикле — деплой основного API
(docker restart tradein-backend) не должен обрывать эту петлю на середине, как и
API не должен блокироваться долгим poll'ом. Тот же паттерн, что и
`scheduler_main.py` (#1182) для scraper'ов — отдельный контейнер с тем же образом,
другая команда.

Запуск: python -m app.tgbot_main

Kill-switch: TELEGRAM_BOT_TOKEN пуст (дефолт) → воркер логирует «disabled» и
блокируется на `wait_for_shutdown()` (idle, ~0 CPU) — НЕ `sys.exit(0)`. Сервис в
compose поднят с `restart: unless-stopped`, который рестартует контейнер
независимо от кода выхода — чистый exit(0) без токена дал бы бесконечный
рестарт-луп. Idle-блокировка держит процесс живым (автозапуск после ребута VPS
работает штатно через restart-policy) без CPU-луп и без спама рестартов;
SIGTERM просто убивает процесс — восстанавливать здесь нечего (bridge-задача
не запущена).
"""

from __future__ import annotations

import asyncio
import logging
import os
import signal
from contextlib import suppress
from typing import Any

from app.core.config import settings
from app.core.db import SessionLocal
from app.core.shutdown import request_shutdown, shutdown_requested, wait_for_shutdown
from app.services.tgbot.bridge import run_poll_loop
from app.services.tgbot.client import TelegramClient

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)

# httpx INFO-логи печатают ПОЛНЫЙ request URL, включая Telegram Bot API токен
# в пути (https://api.telegram.org/bot<id>:<secret>/...) — `httpx: HTTP Request:
# POST https://api.telegram.org/bot<TOKEN>/getMe "HTTP/1.1 401 Unauthorized"`.
# При бесконечном long-polling'e это боевой токен в `docker logs` каждые ~30с.
# WARNING+ у httpx не логирует URL запроса (#tgsupport review, воспроизведено).
logging.getLogger("httpx").setLevel(logging.WARNING)

logger = logging.getLogger(__name__)

# Тот же safety-net паттерн, что scheduler_main.py — ниже docker stop_grace_period.
_DRAIN_TIMEOUT_S = 100.0

# Мониторинг ошибок — GlitchTip (Sentry-совместимый, #396). Только integrations
# без Starlette/FastAPI — здесь нет ASGI-приложения (тот же выбор что scheduler_main).
if settings.glitchtip_dsn:
    import sentry_sdk
    from sentry_sdk.integrations.httpx import HttpxIntegration
    from sentry_sdk.integrations.logging import LoggingIntegration

    from app.observability.sentry_scrub import redact_telegram_bot_token, scrub_pii_event

    def _before_send(event: Any, hint: dict[str, Any]) -> Any:
        """Композиция PII-scrub (form-данные) + Telegram bot-токен redaction
        (#tgsupport review). Токен утекает ДВУМЯ независимыми векторами, которые
        `include_local_variables=False` ниже и этот хук закрывают вместе:
          1. `include_local_variables=True` (sentry_sdk default) кладёт stack-frame
             locals (`self._base`/`url` в `TelegramClient._request`) в traceback —
             закрыто через `include_local_variables=False` в `sentry_sdk.init`.
          2. `HttpxIntegration` кладёт полный request URL в span `data` (не только
             traceback) — `traces_sample_rate=0.0` спасает СЕЙЧАС, но молча
             перестанет спасать, если трейсинг когда-нибудь включат. Regex-редактор
             — belt-and-suspenders на случай #1 (если include_local_variables
             случайно вернут) И на span data.
        """
        scrubbed = scrub_pii_event(event, hint)
        if scrubbed is None:
            return None
        return redact_telegram_bot_token(scrubbed, hint)

    sentry_sdk.init(
        dsn=settings.glitchtip_dsn,
        environment=settings.environment,
        release=os.getenv("GIT_SHA") or os.getenv("SENTRY_RELEASE") or "unknown",
        traces_sample_rate=0.0,
        send_default_pii=False,
        include_local_variables=False,
        before_send=_before_send,
        integrations=[
            HttpxIntegration(),
            LoggingIntegration(level=logging.INFO, event_level=logging.ERROR),
        ],
    )
    logger.info("GlitchTip monitoring enabled (tgbot_main)")


def _should_run() -> bool:
    """Kill-switch: TELEGRAM_BOT_TOKEN не задан → бот выключен (dev/staging без секрета)."""
    return bool(settings.telegram_bot_token)


async def _run_bridge() -> None:
    client = TelegramClient(settings.telegram_bot_token)
    await run_poll_loop(client, SessionLocal)


async def _await_bridge(task: asyncio.Task[None]) -> None:
    """Кооперативный SIGTERM-drain — идентичная семантика scheduler_main._await_scheduler.

    `run_poll_loop` сам проверяет `shutdown_requested()` между итерациями (между
    getUpdates-вызовами) — long-polling запрос к Telegram (до 30с) докручивается,
    затем цикл выходит сам. Safety-net здесь на случай зависшего HTTP-вызова.
    """
    shutdown_waiter = asyncio.create_task(wait_for_shutdown())
    try:
        await asyncio.wait({task, shutdown_waiter}, return_when=asyncio.FIRST_COMPLETED)
    finally:
        shutdown_waiter.cancel()
        with suppress(asyncio.CancelledError):
            await shutdown_waiter

    if task.done():
        task.result()
        logger.info("tgbot_main: bridge task exited cleanly")
        return

    logger.info(
        "tgbot_main: SIGTERM-drain — waiting up to %.0fs for current poll iteration to finish",
        _DRAIN_TIMEOUT_S,
    )
    try:
        await asyncio.wait_for(task, timeout=_DRAIN_TIMEOUT_S)
        logger.info("tgbot_main: bridge drained and exited cleanly")
    except TimeoutError:
        logger.warning(
            "tgbot_main: drain exceeded %.0fs grace — hard-cancelling bridge task",
            _DRAIN_TIMEOUT_S,
        )
        task.cancel()
        with suppress(asyncio.CancelledError):
            await task


async def _run() -> None:
    task = asyncio.create_task(_run_bridge())

    loop = asyncio.get_running_loop()

    def _on_signal(signum: int) -> None:
        logger.info("tgbot_main: signal %d received — requesting cooperative drain", signum)
        request_shutdown()

    try:
        loop.add_signal_handler(signal.SIGTERM, lambda: _on_signal(signal.SIGTERM))
        loop.add_signal_handler(signal.SIGINT, lambda: _on_signal(signal.SIGINT))
    except NotImplementedError:
        # Windows dev: signal handlers через loop не поддерживаются
        logger.warning("tgbot_main: loop.add_signal_handler not supported (Windows dev)")

    await _await_bridge(task)

    if shutdown_requested():
        logger.info("tgbot_main: bridge drained cleanly (SIGTERM)")
    else:
        logger.info("tgbot_main: bridge task exited")


if __name__ == "__main__":
    if not _should_run():
        # NOT sys.exit(0): compose service has `restart: unless-stopped`, который
        # рестартует контейнер независимо от кода выхода — чистый exit(0) без
        # токена дал бы бесконечный рестарт-луп. Idle-блокировка вместо этого:
        # ~0 CPU, SIGTERM просто убивает процесс (нечего дренировать).
        logger.warning(
            "tgbot_main: TELEGRAM_BOT_TOKEN не задан — бот выключен, "
            "блокируемся на idle (не exit, чтобы не было рестарт-лупа с restart:unless-stopped)"
        )
        asyncio.run(wait_for_shutdown())
    else:
        asyncio.run(_run())
