import asyncio
import unittest
from types import SimpleNamespace

import telegram_module
from telegram.error import NetworkError
from telegram.ext import CallbackQueryHandler, CommandHandler


class TelegramAppMenuTests(unittest.TestCase):
    def test_app_keyboard_callback_data_fits_telegram_limit(self):
        keyboard = telegram_module._app_list_keyboard()
        callback_values = [
            button.callback_data
            for row in keyboard.inline_keyboard
            for button in row
        ]

        self.assertEqual(len(callback_values), len(telegram_module.APP_LABELS))
        self.assertTrue(all(len(value.encode("utf-8")) <= 64 for value in callback_values))

    def test_registers_app_commands_and_callback_handler(self):
        original_token = telegram_module.BOT_TOKEN
        telegram_module.BOT_TOKEN = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
        try:
            app = telegram_module.create_bot()
        finally:
            telegram_module.BOT_TOKEN = original_token

        handlers = [handler for group in app.handlers.values() for handler in group]
        command_handlers = {
            command: handler
            for handler in handlers
            if isinstance(handler, CommandHandler)
            for command in handler.commands
        }
        commands = {
            command for command in command_handlers
        }

        self.assertIn("apps", commands)
        self.assertIn("stats", commands)
        self.assertIn("notify_invite", commands)
        self.assertIn("notify_join", commands)
        self.assertIn("notify_leave", commands)
        self.assertIs(command_handlers["notify_join"].callback, telegram_module.cmd_notify_join)
        self.assertIs(command_handlers["notify_leave"].callback, telegram_module.cmd_notify_leave)
        self.assertIs(command_handlers["start"].callback, telegram_module.cmd_start)
        self.assertIs(command_handlers["help"].callback, telegram_module.cmd_help)
        self.assertIsNot(command_handlers["auto_start"].callback, telegram_module.cmd_auto_start)
        self.assertTrue(any(isinstance(handler, CallbackQueryHandler) for handler in handlers))

    def test_role_help_lists_notification_commands(self):
        self.assertIn("/notify_invite", telegram_module.HELP_TEXT)
        self.assertIn("/notify_join", telegram_module.HELP_TEXT)
        self.assertIn("/notify_remove", telegram_module.HELP_TEXT)
        self.assertIn("/notify_leave", telegram_module.HELP_TEXT)
        self.assertIn("/notify_leave", telegram_module.SUBSCRIBER_HELP_TEXT)
        self.assertNotIn("/auto_start", telegram_module.SUBSCRIBER_HELP_TEXT)

    def test_operational_targets_include_owner_and_subscribers_once(self):
        original_chat_id = telegram_module._runtime_chat_id
        original_subscribers = telegram_module._notification_subscribers
        try:
            telegram_module._runtime_chat_id = "100"
            telegram_module._notification_subscribers = {
                "100": {"chat_id": "100"},
                "200": {"chat_id": "200"},
            }

            targets = telegram_module._get_operational_notification_targets()

            self.assertEqual(targets, ["100", "200"])
        finally:
            telegram_module._runtime_chat_id = original_chat_id
            telegram_module._notification_subscribers = original_subscribers

    def test_operational_message_broadcasts_without_changing_owner(self):
        class FakeBot:
            def __init__(self):
                self.sent = []

            async def send_message(self, **kwargs):
                self.sent.append(kwargs)
                return object()

        original_chat_id = telegram_module._runtime_chat_id
        original_subscribers = telegram_module._notification_subscribers
        original_rate_limit = telegram_module._rate_limit_until
        original_send_counter = telegram_module._send_counter
        bot = FakeBot()
        try:
            telegram_module._runtime_chat_id = "100"
            telegram_module._notification_subscribers = {
                "200": {"chat_id": "200"},
            }
            telegram_module._rate_limit_until = 0

            delivered = asyncio.run(
                telegram_module._broadcast_operational_message(
                    SimpleNamespace(bot=bot),
                    "payment complete",
                )
            )

            self.assertTrue(delivered)
            self.assertEqual([item["chat_id"] for item in bot.sent], ["100", "200"])
            self.assertEqual(telegram_module._runtime_chat_id, "100")
        finally:
            telegram_module._runtime_chat_id = original_chat_id
            telegram_module._notification_subscribers = original_subscribers
            telegram_module._rate_limit_until = original_rate_limit
            telegram_module._send_counter = original_send_counter

    def test_polling_uses_resilient_long_poll_settings(self):
        class FakeUpdater:
            def __init__(self):
                self.kwargs = None

            async def start_polling(self, **kwargs):
                self.kwargs = kwargs

        updater = FakeUpdater()
        asyncio.run(
            telegram_module.start_bot_polling(SimpleNamespace(updater=updater))
        )

        self.assertEqual(
            updater.kwargs["timeout"],
            telegram_module.TELEGRAM_LONG_POLL_SECONDS,
        )
        self.assertEqual(updater.kwargs["bootstrap_retries"], -1)
        self.assertIs(
            updater.kwargs["error_callback"],
            telegram_module._handle_polling_error,
        )

    def test_polling_network_error_is_logged_as_retriable_warning(self):
        with self.assertLogs(telegram_module.logger, level="WARNING") as logs:
            telegram_module._handle_polling_error(NetworkError("read failed"))

        self.assertTrue(any("retrying automatically" in item for item in logs.output))


if __name__ == "__main__":
    unittest.main()
