from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from django.conf import settings as project_settings

from .base import BackendBase


class StripeBackend(BackendBase):
    backend_id = 'stripe'
    backend_verbose_name = _("Stripe")
    backend_display_name = _("Credit Card")
    backend_has_recurring = True

    def get_plan_id(self, period):
        return 'ccvpn_' + period

    def __init__(self, settings):
        self.public_key = settings.get('public_key')
        self.secret_key = settings.get('secret_key')
        self.wh_key = settings.get('wh_key')

        if not self.public_key or not self.secret_key or not self.wh_key:
            raise Exception("Missing keys for stripe backend")

        import stripe
        self.stripe = stripe
        stripe.api_key = self.secret_key

        self.header_image = settings.get('header_image', '')
        self.currency = settings.get('currency', 'EUR')
        self.title = settings.get('title', 'VPN Payment')
        self.backend_enabled = True

    def make_redirect(self, session):
        return '''
            <script src="https://js.stripe.com/v3/"></script>
            <script type="text/javascript">
            document.write("<p>Redirecting to the payment page...</p>");
            var stripe = Stripe("{pk}");
            stripe.redirectToCheckout({{
                sessionId: "{sess}"
            }});
            </script>
            <noscript><p>Please enable JavaScript to use the payment form.</p></noscript>
        '''.format(pk=self.public_key, sess=session['id'])

    def new_payment(self, payment):
        root_url = project_settings.ROOT_URL
        assert root_url

        months = payment.time.days // 30
        if months > 1:
            desc = '{} months for {}'.format(months, payment.user.username)
        else:
            desc = 'One month for {}'.format(payment.user.username)

        session = self.stripe.checkout.Session.create(
            success_url=root_url + reverse('payments:view', args=(payment.id,)),
            cancel_url=root_url + reverse('payments:cancel', args=(payment.id,)),
            payment_method_types=['card'],
            line_items=[
                {
                    'amount': payment.amount,
                    'currency': self.currency.lower(),
                    'name': self.title,
                    'description': desc,
                    'quantity': 1,
                }
            ],
        )
        payment.backend_extid = session['id']
        payment.backend_data = {'session_id': session['id']}
        payment.save()
        return self.make_redirect(session)

    def new_subscription(self, subscr):
        root_url = project_settings.ROOT_URL
        assert root_url

        session = self.stripe.checkout.Session.create(
            success_url=root_url + reverse('payments:return_subscr', args=(subscr.id,)),
            cancel_url=root_url + reverse('payments:cancel_subscr', args=(subscr.id,)),
            client_reference_id='sub_%d'%subscr.id,
            payment_method_types=['card'],
            subscription_data={
                'items': [{
                    'plan': self.get_plan_id(subscr.period),
                    'quantity': 1,
                }],
            },
        )
        subscr.backend_data = {'session_id': session['id']}
        subscr.save()
        return self.make_redirect(session)

    def cancel_subscription(self, subscr):
        if subscr.status not in ('new', 'unconfirmed', 'active'):
            return

        if subscr.backend_extid.startswith('pi'):
            # a session that didn't create a subscription yet (intent)
            pass
        elif subscr.backend_extid.startswith('sub_'):
            # Subscription object
            try:
                self.stripe.Subscription.delete(subscr.backend_extid)
            except self.stripe.error.InvalidRequestError:
                pass
        elif subscr.backend_extid.startswith('cus_'):
            # Legacy Customer object
            try:
                cust = self.stripe.Customer.retrieve(subscr.backend_extid)
            except self.stripe.error.InvalidRequestError:
                return

            try:
                # Delete customer and cancel any active subscription
                cust.delete()
            except self.stripe.error.InvalidRequestError:
                pass
        else:
            raise Exception("Failed to cancel subscription %r" % subscr.backend_extid)

        subscr.status = 'cancelled'
        subscr.save()
        return True

    def refresh_subscription(self, subscr):
        if subscr.backend_extid.startswith('cus_'):
            customer = self.stripe.Customer.retrieve(subscr.backend_extid)
            for s in customer['subscriptions']['data']:
                if s['status'] == 'active':
                    sub = s
                    break
            else:
                return
        elif subscr.backend_extid.startswith('sub_'):
            sub = self.stripe.Subscription.retrieve(subscr.backend_extid)
        else:
            print("unhandled subscription backend extid: {}".format(subscr.backend_extid))
            return

        if sub['status'] == 'canceled':
            subscr.status = 'cancelled'
        if sub['status'] == 'past_due':
            subscr.status = 'error'

    def webhook_session_completed(self, event):
        session = event['data']['object']

        if session['subscription']:
            # Subscription creation
            from payments.models import Payment, Subscription

            sub_id = session['subscription']
            assert sub_id

            parts = session['client_reference_id'].split('_')
            if len(parts) != 2 or parts[0] != 'sub':
                raise Exception("invalid reference id")
            sub_internal_id = int(parts[1])

            # Fetch sub by ID and confirm it
            subscr = Subscription.objects.get(id=sub_internal_id)
            subscr.status = 'active'
            subscr.backend_extid = sub_id
            subscr.set_data('subscription_id', sub_id)
            subscr.save()

        else:
            from payments.models import Payment
            payment = Payment.objects.filter(backend_extid=session['id']).get()

            # the amount is provided server-side, we do not have to check
            payment.paid_amount = payment.amount

            payment.status = 'confirmed'
            payment.status_message = None
            payment.save()
            payment.user.vpnuser.add_paid_time(payment.time)
            payment.user.vpnuser.on_payment_confirmed(payment)
            payment.user.vpnuser.save()

            payment.user.vpnuser.lcore_sync()

    def get_subscription_from_invoice(self, invoice):
        from payments.models import Subscription

        subscription_id = invoice['subscription']
        customer_id = invoice['customer']

        # once it's confirmed, the id to the subscription is stored as extid
        subscr = Subscription.objects.filter(backend_extid=subscription_id).first()
        if subscr:
            return subscr

        # older subscriptions will have a customer id instead
        subscr = Subscription.objects.filter(backend_extid=customer_id).first()
        if subscr:
            return subscr

        return None

    def webhook_payment_succeeded(self, event):
        """ webhook event for a subscription's succeeded payment """
        from payments.models import Payment

        invoice = event['data']['object']
        subscr = self.get_subscription_from_invoice(invoice)
        if not subscr:
            # the subscription does not exist
            # checkout.confirmed event will create it and handle the initial payment
            # return True
            raise Exception("Unknown subscription for invoice %r" % invoice['id'])

        # Prevent making duplicate Payments if event is received twice
        pc = Payment.objects.filter(backend_extid=invoice['id']).count()
        if pc > 0:
            return

        payment = subscr.create_payment()
        payment.status = 'confirmed'
        payment.paid_amount = payment.amount
        payment.backend_extid = invoice['id']
        if invoice['subscription']:
            if isinstance(invoice['subscription'], str):
                payment.backend_sub_id = invoice['subscription']
            else:
                payment.backend_sub_id = invoice['subscription']['id']
        payment.set_data('event_id', event['id'])
        payment.set_data('sub_id', payment.backend_sub_id)
        payment.save()

        payment.user.vpnuser.add_paid_time(payment.time)
        payment.user.vpnuser.on_payment_confirmed(payment)
        payment.user.vpnuser.save()
        payment.save()

        payment.user.vpnuser.lcore_sync()

    def webhook_subscr_update(self, event):
        from payments.models import Subscription
        stripe_sub = event['data']['object']
        sub = Subscription.objects.get(backend_id='stripe', backend_extid=stripe_sub['id'])
        
        if not sub:
            return

        if stripe_sub['status'] == 'canceled':
            sub.status = 'cancelled'
        if stripe_sub['status'] == 'past_due':
            sub.status = 'error'
        sub.save()

    def webhook(self, request):
        payload = request.body
        sig_header = request.META['HTTP_STRIPE_SIGNATURE']

        try:
            event = self.stripe.Webhook.construct_event(
                payload, sig_header, self.wh_key,
            )
        except (ValueError, self.stripe.error.InvalidRequestError, self.stripe.error.SignatureVerificationError):
            return False

        if event['type'] == 'invoice.payment_succeeded':
            self.webhook_payment_succeeded(event)
        if event['type'] == 'checkout.session.completed':
            self.webhook_session_completed(event)
        if event['type'] == 'customer.subscription.deleted':
            self.webhook_subscr_update(event)
        return True

    def get_ext_url(self, payment):
        extid = payment.backend_extid

        if not extid:
            return None

        if extid.startswith('in_'):
            return 'https://dashboard.stripe.com/invoices/%s' % extid
        if extid.startswith('ch_'):
            return 'https://dashboard.stripe.com/payments/%s' % extid

    def get_subscr_ext_url(self, subscr):
        extid = subscr.backend_extid

        if not extid:
            return None

        if extid.startswith('sub_') and self.stripe:
            livemode = False
            try:
                sub = self.stripe.Subscription.retrieve(extid)
                livemode = sub['livemode']
            except Exception:
                pass
            if livemode:
                return 'https://dashboard.stripe.com/subscriptions/' + extid
            else:
                return 'https://dashboard.stripe.com/test/subscriptions/' + extid

        if extid.startswith('cus_'):
            return 'https://dashboard.stripe.com/customers/%s' % subscr.backend_extid
