# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import logging
import math
import yenv

from django.conf import settings
from django.core import exceptions
from django.core.mail import send_mail
from django.core.validators import validate_email
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

from .user import Reporter


__all__ = ['Reward']

logger = logging.getLogger(__name__)


class AssignedRewardsManager(models.Manager):

    def get_queryset(self):
        queryset = super(AssignedRewardsManager, self).get_queryset()
        return queryset.exclude(reporter__isnull=True)


class EffectiveRewardsManager(models.Manager):

    def get_queryset(self):
        queryset = super(EffectiveRewardsManager, self).get_queryset()
        return queryset.exclude(status=Reward.ST_DISCARDED)


@python_2_unicode_compatible
class Reward(models.Model):

    D_SEMS = 1
    D_DPMS = 2
    D_MPMS = 3
    D_PSMS = 4
    D_MOMS = 5
    D_INMS = 6
    D_YMMS = 7
    D_MSMS = 8
    D_VSMS = 9
    D_BMMS = 10

    DEPARTMENTS = (
        (D_SEMS, 'SEMS'),
        (D_DPMS, 'DPMS'),
        (D_MPMS, 'MPMS'),
        (D_PSMS, 'PSMS'),
        (D_MOMS, 'MOMS'),
        (D_INMS, 'INMS'),
        (D_YMMS, 'YMMS'),
        (D_MSMS, 'MSMS'),
        (D_VSMS, 'VSMS'),
        (D_BMMS, 'BMMS'),
    )

    C_RUR = 1
    C_USD = 2
    CURRENCY = (
        # Translators: EN russian rubles
        (C_RUR, _('currency.rur')),
        # Translators: EN US dollar
        (C_USD, _('currency.usd')),
    )
    CURRENCY_ACRONYMS = {C_RUR: 'RUR', C_USD: 'USD'}

    ST_NEW = 0
    ST_PAYABLE = 1
    ST_BAD_DETAILS = 2
    ST_FINISHED = 3
    ST_DISCARDED = 4
    ST_UPLOADED_TO_YT = 5
    STATUS = (
        # Translators: EN New
        (ST_NEW, _('reward.new')),
        # Translators: EN Ready for payment
        (ST_PAYABLE, _('reward.payable')),
        # Translators: EN Bad banking details
        (ST_BAD_DETAILS, _('reward.bad_details')),
        # Translators: EN Payed
        (ST_FINISHED, _('reward.payed')),
        # Translators: EN Discarded
        (ST_DISCARDED, _('reward.discarded')),
        # Translators: EN Upload to YT
        (ST_UPLOADED_TO_YT, _('reward.uploaded_to_yt')),
    )

    # TODY(remedy) Set null=False after no empty fields left
    reporter = models.ForeignKey('core.Reporter',
                                 related_name='rewards',
                                 on_delete=models.PROTECT)
    user_login = models.CharField(max_length=30, blank=True, null=True)
    user_uid = models.BigIntegerField(blank=True, null=True)
    staff_uid = models.BigIntegerField()
    staff_login = models.CharField(max_length=30)
    ticket_code = models.CharField(max_length=128)  # тут лежат сансарные тикеты, их не трогаем
    startrek_ticket_code = models.CharField(default='', max_length=128)
    ticket_number = models.BigIntegerField(blank=True, null=True)
    ticket_info = models.CharField(max_length=128, blank=True)
    vulnerability_type = models.ForeignKey(
        'core.Vulnerability', null=True, on_delete=models.PROTECT)
    ticket_created = models.DateTimeField()
    payment_created = models.DateTimeField(auto_now_add=True)
    department = models.IntegerField(choices=DEPARTMENTS, default=D_SEMS)
    # TODY(remedy) Set null=False after field is added
    product = models.ForeignKey('Product',
                                null=True,
                                related_name='rewards',
                                on_delete=models.PROTECT)
    payment_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)  # deprecated
    payment_amount_rur = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    payment_amount_usd = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    payment_currency = models.IntegerField(choices=CURRENCY, null=True, blank=True)
    balance_contract_created = models.DateTimeField(null=True, blank=True)
    balance_error_text = models.TextField(null=True, blank=True)
    balance_contract_attempts = models.IntegerField(default=settings.BALANCE_CONTRACT_CREATION_ATTEMPTS)
    status = models.IntegerField(choices=STATUS, default=ST_NEW)
    points = models.IntegerField(default=0)
    comment = models.TextField(blank=True)
    protocol = models.ForeignKey(
        'core.Protocol', related_name='rewards',
        default=None, null=True, blank=True,
        on_delete=models.SET_NULL)
    award_bonus = models.ForeignKey(
        'core.UserBadge', default=None, null=True, blank=True,
        on_delete=models.SET_NULL)

    objects = models.Manager()
    assigned = AssignedRewardsManager()
    effective = EffectiveRewardsManager()

    class Meta:
        ordering = ('-payment_created',)

    def __str__(self):
        return '%s' % self.vulnerability_type

    @property
    def effective_payment_amount(self):
        if self.payment_currency is None:
            return None  # вылюта ещё не определена

        if self.payment_currency == self.C_RUR:
            return self.payment_amount_rur
        elif self.payment_currency == self.C_USD:
            return self.payment_amount_usd
        else:
            raise ValueError('Incorrect payment_currency for reward with id={}: {}'.format(
                self.id,
                self.payment_currency
            ))

    @property
    def humanized_payment_currency(self):
        if self.payment_currency is None:
            return _('reward.currency_not_yet_specified')
        return self.CURRENCY_ACRONYMS.get(self.payment_currency, _('reward.unsupported_currency'))

    @property
    def humanized_payment(self):
        """Для вывода в списке выплат"""
        if self.payment_currency == self.C_RUR:
            return '₽{}'.format(self.payment_amount_rur)
        elif self.payment_currency == self.C_USD:
            return '${}'.format(self.payment_amount_usd)
        elif self.payment_currency is None:
            return '₽{}/${}'.format(self.payment_amount_rur, self.payment_amount_usd)
        else:
            return _('reward.unsupported_currency')

    def get_full_ticket_info(self):
        ticket_key = '{}-{}'.format(settings.STARTREK_REWARD_QUEUE, self.startrek_ticket_code)
        return '{ticket_code}. {ticket_info}'.format(
            ticket_code=ticket_key,
            ticket_info=self.ticket_info,
        )

    @property
    def startrek_ticket_url(self):
        return 'https://{}/{}-{}'.format(
            settings.STARTREK_FRONTEND_HOST,
            settings.STARTREK_REWARD_QUEUE,
            self.startrek_ticket_code,
        )

    def get_reporter(self):
        if self.reporter:
            return self.reporter
        # TODO(remedy) Remove when all Reward->Reporter links are created.
        uid = self.user_uid
        login = self.user_login
        if uid:
            return Reporter.objects.get(uid=uid)
        else:
            try:
                validate_email(login)
            except exceptions.ValidationError:
                pass
            else:
                return Reporter.objects.get(email=login)
            return Reporter.objects.get(username=login)
        raise Reporter.DoesNotExist('%s[%s]' % (login, uid))

    def get_reporter_safe(self):
        """Wrap get_reporter in try..except block."""
        try:
            return self.get_reporter()
        except Reporter.DoesNotExist:
            logger.warn('Reporter does not exist: %(user_login)s[%(user_uid)s]', vars(self))
            return None
        except exceptions.MultipleObjectsReturned:
            logger.warn('Multiple reporters found: %(user_login)s[%(user_uid)s]', vars(self))
            return None
    get_reporter_safe.short_description = _('Reporter')

    def apply_badge_bonus(self):
        if self.reporter and self.award_bonus is None:
            award = self.reporter.userbadge_set.filter(
                badge__name_en=settings.YANDEX_SPONSORED_BADGE_NAME,
                deactivated__isnull=True).last()
            if award:
                self.award_bonus = award

    def _notify_maillist_about_payment_issues(self):
        subject = 'Reward {}: payment issues'.format(self.pk)
        if yenv.type == 'testing':
            subject = '[TESTING] ' + subject
        errors = self.balance_error_text
        reward_link = settings.INTERNAL_PAYMENT_INFO_EDIT_URL % self.pk
        text = '''
Выплата: {}
Произошла ошибка при заведении платёжного договора в Балансе:

%%{}%%

Если ошибка связана с неправильным заполнением платёжных данных, требуется связаться с пользователем для их исправления.
        '''.format(reward_link, errors).strip('\n')
        from_email = settings.FROM_EMAIL
        recipients = [settings.BALANCE_ERRORS_MAILLIST]
        send_mail(subject, text, from_email, recipients, fail_silently=False)

    def create_balance_data(self):
        from core.utils.balance import (BalanceClient, BadClientDetailsError,
                                        BalanceInternalError, BalancePushError, BugbountyInternalError)
        from app.utils.startrek import notify_about_payment_issues  # internal-only
        assert self.status == self.ST_NEW
        operations_log = []
        try:
            operations_log.append('Started creating balance data')
            client = BalanceClient()
            operations_log.append('Creating client')
            client.create_client(self.reporter)
            operations_log.append('Creating person')
            client.create_person(self.reporter)
            operations_log.append('Creating contract')
            client.create_contract(self)
            self.status = self.ST_PAYABLE
            self.balance_contract_created = timezone.now()
            self.balance_contract_attempts = settings.BALANCE_CONTRACT_CREATION_ATTEMPTS  # сбрасываем попытки
            self.save(update_fields=['status', 'balance_contract_created', 'balance_contract_attempts'])
        except (BadClientDetailsError, BalancePushError) as err:
            error_message = unicode(err)
            if isinstance(err, BugbountyInternalError) and err.original_error:
                error_message += '\nOriginal error: type={}; {}'.format(
                    type(err.original_error).__name__,
                    unicode(err.original_error),
                )
                error_message += '\nOriginal Traceback:\n{}'.format(err.original_traceback)
            operations_log.append(error_message)
            self.balance_contract_attempts = max(0, self.balance_contract_attempts - 1)  # на случай ретраев в Startrek
            self.balance_error_text = '\n'.join(operations_log)
            email_sent = False
            if isinstance(err, BadClientDetailsError) or self.balance_contract_attempts == 0:
                try:
                    self._notify_maillist_about_payment_issues()
                except Exception as err:
                    notify_error = 'Could not send error to maillist: type={}; {}'.format(
                        type(err).__name__,
                        unicode(err),
                    )
                    self.balance_error_text += '\n' + notify_error
                    logger.exception('Could not send error to maillist for reward=%s', self.pk)
                else:
                    email_sent = True

                try:
                    notify_about_payment_issues(self)
                except Exception as err:
                    notify_error = 'Could not notify about an issue: type={}; {}'.format(
                        type(err).__name__,
                        unicode(err),
                    )
                    self.balance_error_text += '\n' + notify_error
                    logger.exception('Could not comment about error on ticket for reward=%s', self.pk)
                    if not email_sent:
                        self.save(update_fields=['balance_error_text'])
                        raise  # пропагируем выше, чтобы всё залогировалось и отправилось в Sentry

                self.status = self.ST_BAD_DETAILS
                self.balance_contract_attempts = settings.BALANCE_CONTRACT_CREATION_ATTEMPTS  # сбрасываем попытки

            self.save(update_fields=['status', 'balance_error_text', 'balance_contract_attempts'])
