from socket import gaierror

from celery.utils.log import get_task_logger
from django.conf import settings
from django.core.mail import BadHeaderError, get_connection, send_mail
from django.utils.html import strip_tags

from apps.alerts.models import AlertGroup
from apps.base.utils import live_settings
from apps.email.alert_rendering import build_subject_and_message
from apps.email.models import EmailMessage
from apps.user_management.models import User
from common.custom_celery_tasks import shared_dedicated_queue_retry_task

MAX_RETRIES = 1 if settings.DEBUG else 10
logger = get_task_logger(__name__)


def get_from_email(user):
    if live_settings.EMAIL_FROM_ADDRESS:
        return live_settings.EMAIL_FROM_ADDRESS

    if settings.LICENSE == settings.CLOUD_LICENSE_NAME:
        return "oncall@{}.{}".format(user.organization.stack_slug, settings.EMAIL_FROM_DOMAIN)

    return live_settings.EMAIL_HOST_USER


@shared_dedicated_queue_retry_task(autoretry_for=(Exception,), retry_backoff=True, max_retries=MAX_RETRIES)
def notify_user_async(user_pk, alert_group_pk, notification_policy_pk):
    # imported here to avoid circular import error
    from apps.base.models import UserNotificationPolicy, UserNotificationPolicyLogRecord

    try:
        user = User.objects.get(pk=user_pk)
    except User.DoesNotExist:
        logger.warning(f"User {user_pk} does not exist")
        return

    try:
        alert_group = AlertGroup.objects.get(pk=alert_group_pk)
    except AlertGroup.DoesNotExist:
        logger.warning(f"Alert group {alert_group_pk} does not exist")
        return

    using_fallback_default_notification_policy_step = False

    if notification_policy_pk is None:
        # NOTE: `notification_policy_pk` may be None if the user has no notification policies defined, as
        # email is the default backend used. see `UserNotificationPolicy.get_default_fallback_policy` for more details
        notification_policy = UserNotificationPolicy.get_default_fallback_policy(user)
        using_fallback_default_notification_policy_step = True
    else:
        try:
            notification_policy = UserNotificationPolicy.objects.get(pk=notification_policy_pk)
        except UserNotificationPolicy.DoesNotExist:
            logger.warning(f"User notification policy {notification_policy_pk} does not exist")
            return

    def _create_user_notification_policy_log_record(**kwargs):
        return UserNotificationPolicyLogRecord.objects.create(
            **kwargs, using_fallback_default_notification_policy_step=using_fallback_default_notification_policy_step
        )

    def _create_email_message(**kwargs):
        return EmailMessage.objects.create(
            **kwargs, using_fallback_default_notification_policy_step=using_fallback_default_notification_policy_step
        )

    # create an error log in case EMAIL_HOST is not specified
    if not live_settings.EMAIL_HOST:
        _create_user_notification_policy_log_record(
            author=user,
            type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
            notification_policy=notification_policy,
            alert_group=alert_group,
            reason="Error while sending email",
            notification_step=notification_policy.step,
            notification_channel=notification_policy.notify_by,
        )
        logger.error("Error while sending email: empty EMAIL_HOST env variable")
        return

    emails_left = user.organization.emails_left(user)
    if emails_left <= 0:
        _create_user_notification_policy_log_record(
            author=user,
            type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
            notification_policy=notification_policy,
            alert_group=alert_group,
            reason="Error while sending email",
            notification_step=notification_policy.step,
            notification_channel=notification_policy.notify_by,
            notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_MAIL_LIMIT_EXCEEDED,
        )
        _create_email_message(
            represents_alert_group=alert_group,
            notification_policy=notification_policy,
            receiver=user,
            exceeded_limit=True,
        )
        return

    subject, html_message = build_subject_and_message(alert_group, emails_left)

    message = strip_tags(html_message)
    from_email = get_from_email(user)
    recipient_list = [user.email]

    connection = get_connection(
        host=live_settings.EMAIL_HOST,
        port=live_settings.EMAIL_PORT,
        username=live_settings.EMAIL_HOST_USER,
        password=live_settings.EMAIL_HOST_PASSWORD,
        use_tls=live_settings.EMAIL_USE_TLS,
        use_ssl=live_settings.EMAIL_USE_SSL,
        fail_silently=False,
        timeout=5,
    )

    try:
        send_mail(subject, message, from_email, recipient_list, html_message=html_message, connection=connection)
        _create_email_message(
            represents_alert_group=alert_group,
            notification_policy=notification_policy,
            receiver=user,
            exceeded_limit=False,
        )
    except (gaierror, BadHeaderError) as e:
        # gaierror is raised when EMAIL_HOST is invalid
        # BadHeaderError is raised when there's newlines in the subject
        _create_user_notification_policy_log_record(
            author=user,
            type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
            notification_policy=notification_policy,
            alert_group=alert_group,
            reason="Error while sending email",
            notification_step=notification_policy.step,
            notification_channel=notification_policy.notify_by,
        )
        logger.error(f"Error while sending email: {e}")
        return

    # record success log
    _create_user_notification_policy_log_record(
        author=user,
        type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_SUCCESS,
        notification_policy=notification_policy,
        alert_group=alert_group,
        notification_step=notification_policy.step,
        notification_channel=notification_policy.notify_by,
    )
