import logging
import typing

import requests
from django.db import models

from apps.slack.client import SlackClient
from apps.slack.constants import SLACK_BOT_ID
from apps.slack.errors import (
    SlackAPICannotDMBotError,
    SlackAPIInvalidAuthError,
    SlackAPITokenError,
    SlackAPIUserNotFoundError,
)
from apps.slack.scenarios.notified_user_not_in_channel import NotifiedUserNotInChannelStep
from apps.user_management.models import Organization, User

if typing.TYPE_CHECKING:
    from django.db.models.manager import RelatedManager

logger = logging.getLogger(__name__)


class AllSlackUserIdentityManager(models.Manager):
    use_in_migrations = False

    def get_queryset(self):
        return super().get_queryset()


class SlackUserIdentityManager(models.Manager):
    use_in_migrations = False

    def get_queryset(self):
        return super().get_queryset().filter(counter=1)

    def get(self, **kwargs):
        try:
            instance = super().get(**kwargs, is_restricted=False, is_ultra_restricted=False)
        except SlackUserIdentity.DoesNotExist:
            instance = self.filter(**kwargs).first()
            if instance is None:
                raise SlackUserIdentity.DoesNotExist
        return instance


class SlackUserIdentity(models.Model):
    users: "RelatedManager['User']"

    objects: models.Manager["SlackUserIdentity"] = SlackUserIdentityManager()
    all_objects: models.Manager["SlackUserIdentity"] = AllSlackUserIdentityManager()

    id = models.AutoField(primary_key=True)

    slack_id = models.CharField(max_length=100)

    slack_team_identity = models.ForeignKey(
        "SlackTeamIdentity", on_delete=models.PROTECT, related_name="slack_user_identities"
    )

    cached_slack_email = models.EmailField(blank=True, default="")

    cached_im_channel_id = models.CharField(max_length=100, null=True, default=None)
    cached_phone_number = models.CharField(max_length=20, null=True, default=None)
    cached_country_code = models.CharField(max_length=3, null=True, default=None)
    cached_timezone = models.CharField(max_length=100, null=True, default=None)
    cached_slack_login = models.CharField(max_length=100, null=True, default=None)
    cached_avatar = models.URLField(max_length=200, null=True, default=None)
    cached_name = models.CharField(max_length=200, null=True, default=None)

    phone_from_onboarding = models.BooleanField(default=False)

    cached_is_bot = models.BooleanField(null=True, default=None)

    # Fields from user profile
    profile_real_name_normalized = models.CharField(max_length=200, null=True, default=None)
    profile_display_name = models.CharField(max_length=200, null=True, default=None)
    profile_display_name_normalized = models.CharField(max_length=200, null=True, default=None)
    profile_real_name = models.CharField(max_length=200, null=True, default=None)

    deleted = models.BooleanField(null=True, default=None)
    is_admin = models.BooleanField(null=True, default=None)
    is_owner = models.BooleanField(null=True, default=None)
    is_primary_owner = models.BooleanField(null=True, default=None)
    is_restricted = models.BooleanField(null=True, default=None)
    is_ultra_restricted = models.BooleanField(null=True, default=None)
    is_app_user = models.BooleanField(null=True, default=None)
    has_2fa = models.BooleanField(null=True, default=None)

    main_menu_last_opened_datetime = models.DateTimeField(null=True, default=None)
    counter = models.PositiveSmallIntegerField(default=1)

    is_stranger = models.BooleanField(default=False)
    is_not_found = models.BooleanField(default=False)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["slack_id", "slack_team_identity", "counter"], name="unique_slack_identity_per_team"
            )
        ]

    def __str__(self):
        return self.slack_login

    def send_link_to_slack_message(self, slack_message):
        blocks = [
            {
                "type": "section",
                "text": {
                    "type": "plain_text",
                    "text": "You are invited to look at an alert group!",
                    "emoji": True,
                },
            },
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "action_id": f"{NotifiedUserNotInChannelStep.routing_uid()}",
                        "text": {"type": "plain_text", "text": "➡️ Go to the alert group"},
                        "url": slack_message.permalink,
                        "style": "primary",
                    }
                ],
            },
            {
                "type": "context",
                "elements": [
                    {
                        "type": "mrkdwn",
                        "text": (
                            f"You received this message because you're not a member of <#{slack_message.channel_id}>.\n"
                            "Please join the channel to get notified right in the alert group thread."
                        ),
                    }
                ],
            },
        ]

        sc = SlackClient(self.slack_team_identity)
        return sc.chat_postMessage(
            channel=self.im_channel_id,
            text="You are invited to look at an alert group!",
            blocks=blocks,
        )

    @property
    def slack_verbal(self) -> str | None:
        return (
            self.profile_real_name_normalized
            or self.profile_real_name
            or self.profile_display_name_normalized
            or self.profile_display_name
            or self.cached_name
            or self.cached_slack_login
        )

    @property
    def slack_login(self):
        if self.cached_slack_login is None or self.cached_slack_login == "slack_token_revoked_unable_to_cache_login":
            sc = SlackClient(self.slack_team_identity)
            try:
                result = sc.users_info(user=self.slack_id, team=self.slack_team_identity)
                self.cached_slack_login = result["user"]["name"]
                self.save()
            except SlackAPITokenError:
                self.cached_slack_login = "slack_token_revoked_unable_to_cache_login"
                self.save()
                return "slack_token_revoked_unable_to_cache_login"
            except SlackAPIUserNotFoundError:
                self.cached_slack_login = "user_not_found"
                self.save()
            except SlackAPIInvalidAuthError:
                return "no_enough_permissions_to_retrieve"

        return str(self.cached_slack_login)

    @property
    def timezone(self):
        if self.cached_timezone is None or self.cached_timezone == "None":
            sc = SlackClient(self.slack_team_identity)
            try:
                result = sc.users_info(user=self.slack_id)
                tz_from_slack = result["user"].get("tz", "UTC")
                if tz_from_slack == "None" or tz_from_slack is None:
                    tz_from_slack = "UTC"
                self.cached_timezone = tz_from_slack
                self.save(update_fields=["cached_timezone"])
            except SlackAPITokenError:
                pass
            except requests.exceptions.Timeout:
                # Do not save tz in case of timeout to try to load it later again
                return "UTC"

        return str(self.cached_timezone)

    @property
    def im_channel_id(self):
        if self.cached_im_channel_id is None:
            sc = SlackClient(self.slack_team_identity)
            try:
                result = sc.conversations_open(users=self.slack_id, return_im=True)
                self.cached_im_channel_id = result["channel"]["id"]
                self.save()
            except SlackAPICannotDMBotError:
                pass

        return self.cached_im_channel_id

    def update_profile_info(self):
        sc = SlackClient(self.slack_team_identity)
        logger.info("Update user profile info")
        try:
            result = sc.users_info(user=self.slack_id, team=self.slack_team_identity)
        except SlackAPITokenError as e:
            logger.warning(f"Unable to get user info due token revoked or account inactive: {e}")
            result = None
        else:
            if not self.cached_slack_email and "email" in result["user"]["profile"]:
                self.cached_slack_email = result["user"]["profile"]["email"]
            if "real_name" in result["user"]["profile"]:
                self.profile_real_name = result["user"]["profile"]["real_name"]
            if "real_name_normalized" in result["user"]["profile"]:
                self.profile_real_name_normalized = result["user"]["profile"]["real_name_normalized"]
            if "display_name" in result["user"]["profile"]:
                self.profile_display_name = result["user"]["profile"]["display_name"]
            if "display_name_normalized" in result["user"]["profile"]:
                self.profile_display_name_normalized = result["user"]["profile"]["display_name_normalized"]
            self.cached_avatar = result["user"]["profile"].get("image_512")
            if result["user"].get("is_bot") is True or result["user"].get("id") == SLACK_BOT_ID:
                self.cached_is_bot = True
            self.cached_name = result["user"].get("real_name", result["user"]["name"])
            self.cached_slack_login = result["user"].get("name")
            self.save()
        return result

    def get_slack_username(self):
        if not self.slack_verbal:
            logger.info("Trying to get username from slack")
            result = self.update_profile_info()
            if result is None:
                logger.info("Unable to populate username")
                return None
        return self.slack_verbal or self.cached_slack_email.split("@")[0] or None

    def get_user(self, organization: Organization) -> User | None:
        try:
            user = organization.users.get(slack_user_identity=self)
        except User.DoesNotExist:
            user = None
        return user
