from copy import deepcopy
import logging
import hashlib
from decimal import Decimal, ROUND_UP
from collections import OrderedDict

from django.utils import six
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import get_language_from_request
from django.apps import apps
from django.core.exceptions import ImproperlyConfigured
from django.utils.six.moves.urllib.parse import urlencode
from getpaid.utils import get_domain
from django.db import transaction

from getpaid.backends import PaymentProcessorBase
from getpaid.utils import build_absolute_uri
from getpaid import signals

if six.PY3:
    unicode = str
logger = logging.getLogger(__name__)

class PaymentProcessor(PaymentProcessorBase):
    BACKEND = 'getpaid.backends.epaydk'
    BACKEND_NAME = _(' backend')

    # @see also
        985: u'PLN',
        978: u'EUR',
        208: u'DKK',
        826: u'GBP',
        840: u'USD',
    BACKEND_LOGO_URL = u'' +\

        'da': 1,
        'en': 2,
        'sv': 3,
        'no': 4,
        'kl': 5,
        'is': 6,
        'de': 7,
        'fi': 8,
        'es': 9,
        'fr': 10,
        'pl': 11,
        'it': 12,
        'nl': 13,

    def format_amount(amount):
        """Return formated amount as number of "pennies".

        Currently only currencies with 2 minor units are supported.

        amount_dec = Decimal(amount).quantize(Decimal('.00'),
        with_decimal = u"{0:.2f}".format(amount_dec)
        return u''.join(with_decimal.split('.'))

    def get_currency_by_number(currency_id):
        cur_dict = PaymentProcessor.BACKEND_ACCEPTED_CURRENCY_DICT
        return cur_dict.get(int(currency_id))

    def get_number_for_currency(currency_code):
        for cid, cval in \
            if cval == currency_code:
                return cid

    def amount_to_python(amount_str):
        return Decimal(int(amount_str) / Decimal("100.0"))

    def compute_hash(params):
        The hash you send to (and receive from) ePay must be the value of all
        parameters in the order they are sent + the MD5 key.

        @param params: OrderedDict with values as python unicode objects.

        assert isinstance(params, OrderedDict)
        params = deepcopy(params)
        secret = unicode(PaymentProcessor.get_backend_setting('secret', ''))
        if not secret:
            raise ImproperlyConfigured("epaydk requires `secret` md5 hash"
                                       " setting")
        values = u''
        for key, val in params.items():
            assert isinstance(val, six.text_type),\
                "param {} is not unicode it is {}".format(key, type(val))
            if key == u'hash':
            values += val
        values_secret = (values + secret).encode('utf8')
        sig_hash = hashlib.md5(values_secret).hexdigest()
        return sig_hash

    def is_received_request_valid(params):
        The hash received from ePay is the value of all GET parameters
        received except the parameter hash + the MD5 key.

        @param params: OrderedDict with values as python unicode objects.

        sig_hash = PaymentProcessor.compute_hash(params)
        if 'hash' in params:
            if params['hash'] == sig_hash:
                return True
        return False

    def _get_language_id(self, request, prefered='en'):
        req_lang = get_language_from_request(request) or prefered
        return unicode(self.EPAYDK_LANGUAGE_IDS.get(req_lang, 2))  # 2=en

    def get_gateway_url(self, request):

        `accepturl` - payment accepted for processing.
        `cancelurl` - user closed window before the payment is completed.
        `callbackurl` - is called instantly from the ePay server when
                        the payment is completed.
        merchantnumber = unicode(
            self.get_backend_setting('merchantnumber', ''))
        if not merchantnumber:
            raise ImproperlyConfigured(" requires merchantnumber")

        # According to docs order ID should be a-Z 0-9. Max. 9 characters.
        # We use payment id here as we will have access to order from it.
        payment_id = unicode(

        currency = unicode(PaymentProcessor.get_number_for_currency(

        # timeout in minutes
        timeout = unicode(self.get_backend_setting('timeout', '3'))
        instantcallback = unicode(self.get_backend_setting('instantcallback',

        params = OrderedDict([
            (u'merchantnumber', merchantnumber),
            (u'orderid', payment_id),
            (u'currency', currency),
            (u'amount', PaymentProcessor.format_amount(self.payment.amount)),
            (u'windowstate', u'3'),  # 3 = Full screen
            (u'mobile', u'1'),  # 1 = autodetect
            (u'timeout', timeout),
            (u'instantcallback', instantcallback),

        user_data = {
            u'email': None,
            u'lang': None,

            sender=None, order=self.payment.order, user_data=user_data)

        prefered = user_data['lang'] or 'en'
        params['language'] = self._get_language_id(request, prefered=prefered)

        url_data = {
            'domain': get_domain(request=request),
            'scheme': request.scheme

        params['accepturl'] = build_absolute_uri('getpaid:epaydk:success',

        if not PaymentProcessor.get_backend_setting('callback_secret_path',
            params['callbackurl'] = build_absolute_uri(
                'getpaid:epaydk:online', **url_data

        params['cancelurl'] = build_absolute_uri('getpaid:epaydk:failure',
        params['hash'] = PaymentProcessor.compute_hash(params)

        url = u"{}?{}".format(self.BACKEND_GATEWAY_BASE_URL, urlencode(params))
        return (url, 'GET', {})

    def get_logo_url(self):
        return self.BACKEND_LOGO_URL

    def confirmed(params):
        Payment was confirmed.
        Payment = apps.get_model('getpaid', 'Payment')
        with transaction.atomic():
            payment = Payment.objects.get(id=params['orderid'])
            assert payment.status == 'accepted_for_proc',\
                "Can not confirm payment that was not accepted for processing"
            payment.external_id = params['txnid']
            # payment_datetime = datetime.datetime.combine(params['date'],
            amount = PaymentProcessor.amount_to_python(params['amount'])
            # txnfee = PaymentProcessor.amount_to_python(params['txnfee'])
            payment.amount_paid = amount
            return payment.on_success()

    def accepted_for_processing(payment_id=None):
        Payment was accepted into the queue for processing.
        Payment = apps.get_model('getpaid', 'Payment')
        with transaction.atomic():
            payment = Payment.objects.get(id=payment_id)
            assert payment.status == 'in_progress',\
                "Can not accept payment that is not in progress"

    def cancelled(payment_id=None):
        Payment was cancelled.
        Payment = apps.get_model('getpaid', 'Payment')
        with transaction.atomic():
            payment = Payment.objects.get(id=payment_id)