from django.db import models
from telegram import error

from apps.alerts.models import AlertGroup
from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRecord
from apps.telegram.client import TelegramClient
from apps.telegram.decorators import ignore_reply_to_message_deleted
from apps.telegram.models import TelegramMessage, TelegramToOrganizationConnector
from apps.telegram.tasks import send_link_to_channel_message_or_fallback_to_full_alert_group
from apps.user_management.models import User

ONE_MORE_NOTIFICATION = "One more notification about this 👆"
ALERT_CANT_BE_RENDERED = (
    "You have a new alert group, but Telegram can't render its content! Please check it out: {link}"
)


class TelegramToUserConnector(models.Model):
    user = models.OneToOneField("user_management.User", on_delete=models.CASCADE, related_name="telegram_connection")

    telegram_chat_id = models.BigIntegerField()
    telegram_nick_name = models.CharField(max_length=100, null=True, default=None)
    datetime = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = (("user", "telegram_chat_id"),)

    class NotificationErrorReason:
        USER_WAS_DISABLED = "Telegram user was disabled"
        INVALID_TOKEN = "Invalid token"
        BOT_BLOCKED_BY_USER = "Bot was blocked by the user"
        FORMATTING_ERROR_IN_RENDERED_TEMPLATE = (
            "Notification sent but there was a formatting error in the rendered template"
        )

    @classmethod
    def notify_user(cls, user: User, alert_group: AlertGroup, notification_policy: UserNotificationPolicy) -> None:
        try:
            user_connector = user.telegram_connection
            user_connector.notify(alert_group=alert_group, notification_policy=notification_policy)
        except TelegramToUserConnector.DoesNotExist:
            cls.create_telegram_notification_error(
                alert_group=alert_group,
                user=user,
                notification_policy=notification_policy,
                reason="No linked telegram account",
                error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_IS_NOT_LINKED_TO_SLACK_ACC,
            )

    def notify(self, alert_group: AlertGroup, notification_policy: UserNotificationPolicy) -> None:
        telegram_channel = TelegramToOrganizationConnector.get_channel_for_alert_group(alert_group)

        if telegram_channel is not None:
            # Call this task with a countdown to avoid unnecessary retry when alert group telegram message hasn't been
            # created yet
            send_link_to_channel_message_or_fallback_to_full_alert_group.apply_async(
                kwargs={
                    "alert_group_pk": alert_group.pk,
                    "notification_policy_pk": notification_policy.pk,
                    "user_connector_pk": self.pk,
                },
                countdown=3,
            )
        else:
            self.send_full_alert_group(alert_group=alert_group, notification_policy=notification_policy)

    @staticmethod
    def create_telegram_notification_success(
        alert_group: AlertGroup, user: User, notification_policy: UserNotificationPolicy
    ) -> None:
        UserNotificationPolicyLogRecord.objects.create(
            author=user,
            type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_SUCCESS,
            notification_policy=notification_policy,
            alert_group=alert_group,
            notification_step=notification_policy.step if notification_policy else None,
            notification_channel=notification_policy.notify_by if notification_policy else None,
        )

    @staticmethod
    def create_telegram_notification_error(
        alert_group: AlertGroup,
        user: User,
        notification_policy: UserNotificationPolicy,
        error_code: int,
        reason: str | None,
    ) -> None:
        UserNotificationPolicyLogRecord.objects.create(
            author=user,
            type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
            notification_policy=notification_policy,
            alert_group=alert_group,
            notification_error_code=error_code,
            reason=reason,
            notification_step=notification_policy.step if notification_policy else None,
            notification_channel=notification_policy.notify_by if notification_policy else None,
        )

    # send the actual alert group and log to user's DM
    def send_full_alert_group(self, alert_group: AlertGroup, notification_policy: UserNotificationPolicy) -> None:
        try:
            telegram_client = TelegramClient()
        except error.InvalidToken:
            TelegramToUserConnector.create_telegram_notification_error(
                alert_group,
                self.user,
                notification_policy,
                UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
                reason=self.NotificationErrorReason.INVALID_TOKEN,
            )
            return

        old_alert_group_message = alert_group.telegram_messages.filter(
            chat_id=self.telegram_chat_id,
            message_type__in=[
                TelegramMessage.PERSONAL_MESSAGE,
                TelegramMessage.FORMATTING_ERROR,
            ],
        ).first()

        if old_alert_group_message is None:
            try:
                telegram_client.send_message(
                    chat_id=self.telegram_chat_id,
                    message_type=TelegramMessage.PERSONAL_MESSAGE,
                    alert_group=alert_group,
                )
            except error.BadRequest:
                TelegramToUserConnector.create_telegram_notification_error(
                    alert_group,
                    self.user,
                    notification_policy,
                    UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORMATTING_ERROR,
                    reason=self.NotificationErrorReason.FORMATTING_ERROR_IN_RENDERED_TEMPLATE,
                )
                telegram_client.send_message(
                    chat_id=self.telegram_chat_id,
                    message_type=TelegramMessage.FORMATTING_ERROR,
                    alert_group=alert_group,
                )
            except error.Unauthorized as e:
                if TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER]):
                    TelegramToUserConnector.create_telegram_notification_error(
                        alert_group,
                        self.user,
                        notification_policy,
                        UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
                        reason=self.NotificationErrorReason.BOT_BLOCKED_BY_USER,
                    )
                elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.INVALID_TOKEN]):
                    TelegramToUserConnector.create_telegram_notification_error(
                        alert_group,
                        self.user,
                        notification_policy,
                        UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
                        reason=self.NotificationErrorReason.INVALID_TOKEN,
                    )
                elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED]):
                    TelegramToUserConnector.create_telegram_notification_error(
                        alert_group,
                        self.user,
                        notification_policy,
                        UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
                        reason=self.NotificationErrorReason.USER_WAS_DISABLED,
                    )
                else:
                    raise e
            else:
                TelegramToUserConnector.create_telegram_notification_success(
                    alert_group, self.user, notification_policy
                )
        else:
            self._nudge_about_alert_group_message(telegram_client, old_alert_group_message)
            TelegramToUserConnector.create_telegram_notification_success(alert_group, self.user, notification_policy)

    # send DM message with the link to the alert group post in channel
    def send_link_to_channel_message(self, alert_group: AlertGroup, notification_policy: UserNotificationPolicy):
        try:
            telegram_client = TelegramClient()
        except error.InvalidToken:
            TelegramToUserConnector.create_telegram_notification_error(
                alert_group,
                self.user,
                notification_policy,
                UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
                reason=self.NotificationErrorReason.INVALID_TOKEN,
            )
            return

        try:
            telegram_client.send_message(
                chat_id=self.telegram_chat_id,
                message_type=TelegramMessage.LINK_TO_CHANNEL_MESSAGE,
                alert_group=alert_group,
            )
        except error.BadRequest:
            # incorrect format of the title, so do not include it to the link to channel message
            telegram_client.send_message(
                chat_id=self.telegram_chat_id,
                message_type=TelegramMessage.LINK_TO_CHANNEL_MESSAGE_WITHOUT_TITLE,
                alert_group=alert_group,
            )
        except error.Unauthorized as e:
            if TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.BOT_WAS_BLOCKED_BY_USER]):
                TelegramToUserConnector.create_telegram_notification_error(
                    alert_group,
                    self.user,
                    notification_policy,
                    UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_BOT_IS_DELETED,
                    reason=self.NotificationErrorReason.BOT_BLOCKED_BY_USER,
                )
            elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.INVALID_TOKEN]):
                TelegramToUserConnector.create_telegram_notification_error(
                    alert_group,
                    self.user,
                    notification_policy,
                    UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_TOKEN_ERROR,
                    reason=self.NotificationErrorReason.INVALID_TOKEN,
                )
            elif TelegramClient.error_message_is(e, [TelegramClient.UnauthorizedMessage.USER_IS_DEACTIVATED]):
                TelegramToUserConnector.create_telegram_notification_error(
                    alert_group,
                    self.user,
                    notification_policy,
                    UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
                    reason=self.NotificationErrorReason.USER_WAS_DISABLED,
                )
            else:
                raise e
        else:
            TelegramToUserConnector.create_telegram_notification_success(alert_group, self.user, notification_policy)

    @staticmethod
    @ignore_reply_to_message_deleted
    def _nudge_about_alert_group_message(telegram_client: TelegramClient, message: TelegramMessage) -> None:
        """Nudge the user about existing alert group message, without sending the full alert group content again."""
        telegram_client.send_raw_message(
            chat_id=message.chat_id, reply_to_message_id=message.message_id, text=ONE_MORE_NOTIFICATION
        )
