import logging
from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
from datetime import timedelta

from ccvpn.common import get_price
from .backends import BackendBase

logger = logging.getLogger(__name__)

backends_settings = settings.PAYMENTS_BACKENDS
assert isinstance(backends_settings, dict)

CURRENCY_CODE, CURRENCY_NAME = settings.PAYMENTS_CURRENCY

STATUS_CHOICES = (
    ('new', _("Waiting for payment")),
    ('confirmed', _("Confirmed")),
    ('cancelled', _("Cancelled")),
    ('rejected', _("Rejected by processor")),
    ('error', _("Payment processing failed")),
)

# A Subscription is created with status='new'. When getting back from PayPal,
# it may get upgraded to 'unconfirmed'. It will be set 'active' with the first
# confirmed payment.
# 'unconfirmed' exists to prevent creation of a second Subscription while
# waiting for the first one to be confirmed.
SUBSCR_STATUS_CHOICES = (
    ('new', _("Created")),
    ('unconfirmed', _("Waiting for payment")),
    ('active', _("Active")),
    ('cancelled', _("Cancelled")),
    ('error', _("Error")),
)

SUBSCR_PERIOD_CHOICES = (
    ('3m', _("Every 3 months")),
    ('6m', _("Every 6 months")),
    ('12m', _("Every year")),
)

BACKEND_CLASSES = BackendBase.__subclasses__()

# All known backends (classes)
BACKENDS = {}
BACKEND_CHOICES = []

# All enabled backends (configured instances)
ACTIVE_BACKENDS = {}
ACTIVE_BACKEND_CHOICES = []

logger.info("loading payment backends...")
for cls in BACKEND_CLASSES:
    name = cls.backend_id
    prefix = "Backend {:<8}:".format(name)
    assert isinstance(name, str)

    if name not in backends_settings:
        logger.info("%s ☒ disabled (no settings)", prefix)
        continue

    backend_settings = backends_settings.get(name, {})
    for k, v in backend_settings.items():
        if hasattr(v, '__call__'):
            backend_settings[k] = v()

    if not backend_settings.get('enabled'):
        logger.info("%s ☒ disabled (by settings)", prefix)
        continue

    obj = cls(backend_settings)

    BACKENDS[name] = obj
    BACKEND_CHOICES.append((name, cls.backend_verbose_name))

    if obj.backend_enabled:
        ACTIVE_BACKENDS[name] = obj
        ACTIVE_BACKEND_CHOICES.append((name, cls.backend_verbose_name))
        logger.info("%s ☑ initialized", prefix)
    else:
        logger.info("%s ☒ disabled (initialization failed)", prefix)

BACKEND_CHOICES = sorted(BACKEND_CHOICES, key=lambda x: x[0])
ACTIVE_BACKEND_CHOICES = sorted(ACTIVE_BACKEND_CHOICES, key=lambda x: x[0])

logger.info("finished. %d/%d backends active", len(ACTIVE_BACKENDS), len(BACKEND_CLASSES))


def period_months(p):
    return {
        '3m': 3,
        '6m': 6,
        '12m': 12,
    }[p]


class Payment(models.Model):
    """ Just a payment.
    If subscription is not null, it has been automatically issued.
    backend_extid is the external transaction ID, backend_data is other
    things that should only be used by the associated backend.
    """
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    backend_id = models.CharField(max_length=16, choices=BACKEND_CHOICES)
    status = models.CharField(max_length=16, choices=STATUS_CHOICES, default='new')
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    confirmed_on = models.DateTimeField(null=True, blank=True)
    amount = models.IntegerField()
    paid_amount = models.IntegerField(default=0)
    time = models.DurationField()
    subscription = models.ForeignKey('Subscription', null=True, blank=True, on_delete=models.CASCADE)
    status_message = models.TextField(blank=True, null=True)

    backend_extid = models.CharField(max_length=64, null=True, blank=True)
    backend_data = JSONField(blank=True)

    @property
    def currency_code(self):
        return CURRENCY_CODE

    @property
    def currency_name(self):
        return CURRENCY_NAME

    @property
    def backend(self):
        """ Returns a global instance of the backend
        :rtype: BackendBase
        """
        return BACKENDS[self.backend_id]

    def get_amount_display(self):
        return '%.2f %s' % (self.amount / 100, CURRENCY_NAME)

    @property
    def is_confirmed(self):
        return self.status == 'confirmed'

    def confirm(self):
        self.user.vpnuser.add_paid_time(self.time)
        self.user.vpnuser.on_payment_confirmed(self)
        self.user.vpnuser.save()
        self.update_status('confirmed')

    def refund(self):
        self.user.vpnuser.remove_paid_time(self.time)
        self.user.vpnuser.save()
        self.update_status('refunded')

    def update_status(self, status, message=None):
        assert any(c[0] == status for c in STATUS_CHOICES)
        self.status = status
        self.status_message = message

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

    @classmethod
    def create_payment(self, backend_id, user, months):
        payment = Payment(
            user=user,
            backend_id=backend_id,
            status='new',
            time=timedelta(days=30 * months),
            amount=get_price() * months
        )
        return payment


class Subscription(models.Model):
    """ Recurring payment subscription. """
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    backend_id = models.CharField(max_length=16, choices=BACKEND_CHOICES)
    created = models.DateTimeField(auto_now_add=True)
    period = models.CharField(max_length=16, choices=SUBSCR_PERIOD_CHOICES)
    last_confirmed_payment = models.DateTimeField(blank=True, null=True)
    status = models.CharField(max_length=16, choices=SUBSCR_STATUS_CHOICES, default='new')

    backend_extid = models.CharField(max_length=64, null=True, blank=True)
    backend_data = JSONField(blank=True)

    @property
    def backend(self):
        """ Returns a global instance of the backend
        :rtype: BackendBase
        """
        return BACKENDS[self.backend_id]

    @property
    def months(self):
        return period_months(self.period)

    @property
    def period_amount(self):
        return self.months * get_price()

    @property
    def next_renew(self):
        """ Approximate date of the next payment """
        if self.last_confirmed_payment:
            return self.last_confirmed_payment + timedelta(days=self.months * 30)
        return self.created + timedelta(days=self.months * 30)

    @property
    def monthly_amount(self):
        return get_price()

    def create_payment(self):
        payment = Payment(
            user=self.user,
            backend_id=self.backend_id,
            status='new',
            time=timedelta(days=30 * self.months),
            amount=get_price() * self.months,
            subscription=self,
        )
        return payment


