from __future__ import unicode_literals
import binascii

from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import redirect

from ..core import BasicProvider
from .. import PaymentStatus


class SagepayProvider(BasicProvider):
    '''
    sagepay.com payment provider

    vendor:
        vendor name
    encryption_key:
        encryption key
    endpoint:
        gateway URL to post transaction data to
    '''
    _version = '3.00'
    _action = 'https://test.sagepay.com/Simulator/VSPFormGateway.asp'

    def __init__(self, vendor, encryption_key, endpoint=_action, **kwargs):
        self._vendor = vendor
        self._enckey = encryption_key.encode('utf-8')
        self._action = endpoint
        super(SagepayProvider, self).__init__(**kwargs)
        if not self._capture:
            raise ImproperlyConfigured(
                'Sagepay does not support pre-authorization.')

    def _get_cipher(self):
        backend = default_backend()
        return Cipher(algorithms.AES(self._enckey), modes.CBC(self._enckey),
                      backend=backend)

    def _get_padding(self):
        return padding.PKCS7(128)

    def aes_enc(self, data):
        data = data.encode('utf-8')
        padder = self._get_padding().padder()
        data = padder.update(data) + padder.finalize()
        encryptor = self._get_cipher().encryptor()
        enc = encryptor.update(data) + encryptor.finalize()
        return b"@" + binascii.hexlify(enc)

    def aes_dec(self, data):
        data = data.lstrip(b'@')
        data = binascii.unhexlify(data)
        decryptor = self._get_cipher().decryptor()
        data = decryptor.update(data) + decryptor.finalize()
        return data.decode('utf-8')

    def get_hidden_fields(self, payment):
        payment.save()
        return_url = self.get_return_url(payment)
        data = {
            'VendorTxCode': payment.pk,
            'Amount': "%.2f" % (payment.total,),
            'Currency': payment.currency,
            'Description': "Payment #%s" % (payment.pk,),
            'SuccessURL': return_url,
            'FailureURL': return_url,
            'BillingSurname': payment.billing_last_name,
            'BillingFirstnames': payment.billing_first_name,
            'BillingAddress1': payment.billing_address_1,
            'BillingAddress2': payment.billing_address_2,
            'BillingCity': payment.billing_city,
            'BillingPostCode': payment.billing_postcode,
            'BillingCountry': payment.billing_country_code,
            'DeliverySurname': payment.billing_last_name,
            'DeliveryFirstnames': payment.billing_first_name,
            'DeliveryAddress1': payment.billing_address_1,
            'DeliveryAddress2': payment.billing_address_2,
            'DeliveryCity': payment.billing_city,
            'DeliveryPostCode': payment.billing_postcode,
            'DeliveryCountry': payment.billing_country_code}
        if payment.billing_country_code == 'US':
            data['BillingState'] = payment.billing_country_area
            data['DeliveryState'] = payment.billing_country_area
        udata = "&".join("%s=%s" % kv for kv in data.items())
        crypt = self.aes_enc(udata)
        return {'VPSProtocol': self._version, 'TxType': 'PAYMENT',
                'Vendor': self._vendor, 'Crypt': crypt}

    def process_data(self, payment, request):
        udata = self.aes_dec(request.GET['crypt'])
        data = {}
        for kv in udata.split('&'):
            k, v = kv.split('=')
            data[k] = v
        success_url = payment.get_success_url()
        if payment.status == PaymentStatus.WAITING:
            # If the payment is not in waiting state, we probably have a page reload.
            # We should neither throw 404 nor alter the payment again in such case.
            if data['Status'] == 'OK':
                payment.captured_amount = payment.total
                payment.change_status(PaymentStatus.CONFIRMED)
                return redirect(success_url)
            else:
                # XXX: We should recognize AUTHENTICATED and REGISTERED in the future.
                payment.change_status(PaymentStatus.REJECTED)
                return redirect(payment.get_failure_url())
        return redirect(success_url)
