#
# Copyright 2012-2016 PressLabs SRL
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#
import hashlib
import hmac
from datetime import datetime
from collections import OrderedDict

import requests

from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver

from payu.signals import (
    payment_completed,
    payment_authorized,
    payment_flagged,
    alu_token_created,
)
from payu.conf import (
    PAYU_PAYMENT_STATUS,
    PAYU_IDN_URL,
    PAYU_MERCHANT,
    PAYU_MERCHANT_KEY,
)


class PayUIPN(models.Model):
    REFNO = models.CharField(max_length=9, verbose_name="ePayment reference")
    REFNOEXT = models.CharField(max_length=100, verbose_name="Merchant reference")
    ORDERNO = models.CharField(max_length=6, verbose_name="Merchant order #")
    ORDERSTATUS = models.CharField(
        max_length=18, choices=PAYU_PAYMENT_STATUS, verbose_name="Status"
    )
    HASH = models.CharField(max_length=64)
    PAYMETHOD_CODE = models.CharField(max_length=10, verbose_name="Payment method code")

    SALEDATE = models.DateTimeField(blank=True, null=True, verbose_name="Sale date")
    COMPLETE_DATE = models.DateTimeField(
        blank=True, null=True, verbose_name="Complete date"
    )
    PAYMENTDATE = models.DateTimeField(
        blank=True, null=True, verbose_name="Payment date"
    )

    PAYMETHOD = models.CharField(
        blank=True, null=True, max_length=100, verbose_name="Payment method"
    )
    FIRSTNAME = models.CharField(
        blank=True, null=True, max_length=40, help_text="Client's first name"
    )
    LASTNAME = models.CharField(
        blank=True, null=True, max_length=40, help_text="Client's last name"
    )
    IDENTITY_NO = models.CharField(
        blank=True,
        null=True,
        max_length=15,
        help_text="Customer ID Card series and \
                                   number (Series / Number - available \
                                   only for Romanian customers)",
    )
    IDENTITY_ISSUER = models.CharField(
        blank=True,
        null=True,
        max_length=100,
        help_text="IDENTITY_NO ID Card \
                                       issuer authority ",
    )
    CARD_TYPE = models.CharField(
        blank=True,
        null=True,
        max_length=100,
        help_text='Used credit card type. \
                                 Ex: "Visa" or "MasterCard"',
    )
    IDENTITY_CNP = models.CharField(
        blank=True,
        null=True,
        max_length=13,
        help_text="Customer's personal numeric \
                                    code, available only for Romanian customers.",
    )
    COMPANY = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Company name (maximum length: \
                                             40 characters) ",
    )
    REGISTRATIONNUMBER = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Company's Commerce \
                                          Registry registration number \
                                          (maximum length: 40 characters)",
    )
    FISCALCODE = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Unique Registration Number / \
                                  VAT ID (maximum length: 40 characters)",
    )
    CBANKNAME = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Company's Bank (maximum \
                                 length: 40 characters) ",
    )
    CBANKACCOUNT = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Company's Bank Account \
                                    (maximum length: 50 characters)",
    )
    ADDRESS1 = models.CharField(
        blank=True,
        null=True,
        max_length=100,
        help_text="Address (maximum length: 100 \
                                characters)",
    )
    ADDRESS2 = models.CharField(
        blank=True,
        null=True,
        max_length=100,
        help_text="Additional Address info \
                                (maximum length: 100 characters)",
    )
    CITY = models.CharField(
        blank=True,
        null=True,
        max_length=30,
        help_text="City (maximum length: 30 characters)",
    )
    STATE = models.CharField(
        blank=True,
        null=True,
        max_length=30,
        help_text="State/Sector/County (maximum \
                             length: 30 characters)",
    )
    ZIPCODE = models.CharField(
        blank=True,
        null=True,
        max_length=20,
        help_text="ZIP/Postal Code (maximum length: \
                               20 characters)",
    )
    COUNTRY = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Country (maximum length: 50 \
                               characters)",
    )
    COUNTRY_CODE = models.CharField(
        blank=True,
        null=True,
        max_length=10,
        help_text="Country (maximum length: 10 \
                                    characters)",
    )
    PHONE = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Phone number (maximum length: 40 \
                             characters)",
    )
    FAX = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Fax number (maximum length: 40 \
                           characters)",
    )
    CUSTOMEREMAIL = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Customer's e-mail address \
                                     (maximum length: 40 characters)",
    )
    FIRSTNAME_D = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="First name (maximum length: \
                                   40 characters) ",
    )
    LASTAME_D = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Last Name (maximum length: 40 \
                                 characters)",
    )
    COMPANY_D = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Company (maximum length: 50 \
                                 characters)",
    )
    ADDRESS1_D = models.CharField(
        blank=True,
        null=True,
        max_length=100,
        help_text="Address (maximum length: 100 \
                                  characters)",
    )
    ADDRESS2_D = models.CharField(
        blank=True,
        null=True,
        max_length=100,
        help_text="Additional address info \
                                  (maximum length: 100 characters)",
    )
    CITY_D = models.CharField(
        blank=True,
        null=True,
        max_length=30,
        help_text="City (maximum length: 30 \
                              characters)",
    )
    STATE_D = models.CharField(
        blank=True,
        null=True,
        max_length=30,
        help_text="State/Sector/County (maximum \
                               length: 30 characters)",
    )
    ZIPCODE_D = models.CharField(
        blank=True,
        null=True,
        max_length=20,
        help_text="ZIP/Postal Code (maximum \
                                 length: 20 characters)",
    )
    COUNTRY_D = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Country (maximum length: 50 \
                                 characters)",
    )
    COUNTRY_D_CODE = models.CharField(
        blank=True,
        null=True,
        max_length=10,
        help_text="Country (maximum length: \
                                      10 characters)",
    )
    PHONE_D = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Phone number (maximum length: 40 \
                               characters)",
    )
    EMAIL_D = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="E-mail (maximum length: 40 \
                               characters)",
    )
    IPADDRESS = models.CharField(
        blank=True,
        null=True,
        max_length=250,
        help_text="Client's IP Address (maximum \
                                 length: 250 characters)",
    )
    IPCOUNTRY = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Client's IP Country (maximum \
                                 length: 50 characters)",
    )
    COMPLETE_DATE = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="The order completion date, \
                                     in the following format: Y-m-d H:i:s \
                                     (2012-04-26 15:02:28) .",
    )
    CURRENCY = models.CharField(
        blank=True,
        null=True,
        max_length=10,
        help_text="The currency in which the order \
                                has been processed. Possible values: RON, \
                                USD, EUR.",
    )
    LANGUAGE = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="The language in which the order \
                                has been processed. Possible values: ro, \
                                en, fr, de, it.",
    )

    IPN_PID = models.TextField(
        blank=True,
        null=True,
        help_text="Array with the ID Codes of the \
                               ordered products, in the PayU database (PayU \
                               reference) ",
    )
    IPN_PNAME = models.TextField(
        blank=True, null=True, help_text="Array with product names "
    )
    IPN_PCODE = models.TextField(
        blank=True,
        null=True,
        help_text="Array with the product codes \
                                 assigned by the vendor in the system (vendor \
                                 reference)",
    )
    IPN_INFO = models.TextField(
        blank=True,
        null=True,
        help_text="Array with additional \
                                information sent for each ordered product (if \
                                they have been sent to PayU)",
    )
    IPN_QTY = models.TextField(
        blank=True, null=True, help_text="Array with the product quantities"
    )
    IPN_PRICE = models.TextField(
        blank=True,
        null=True,
        help_text="Array with unit prices per \
                                 product (without VAT), in RON, with \
                                 period/full-stop (.) as decimal place \
                                 separator",
    )
    IPN_VAT = models.TextField(
        blank=True,
        null=True,
        help_text='Array with VAT values per \
                               product, with period "." as decimal place \
                               separator',
    )
    IPN_VER = models.TextField(
        blank=True,
        null=True,
        help_text="Array with product versions \
                               (maximum length: 50 characters)",
    )
    IPN_DISCOUNT = models.TextField(
        blank=True,
        null=True,
        help_text="Array with the amounts with \
                                    which there has been made a discount \
                                    in a promotion. Including VAT. ",
    )
    IPN_PROMONAME = models.TextField(
        blank=True,
        null=True,
        help_text="Array with the names of the \
                                     promotions in which the discounts \
                                     specified above have been made.",
    )
    IPN_PROMOCODE = models.TextField(
        blank=True,
        null=True,
        help_text="Array with the code of the \
                                     promotions in which the discounts \
                                     specified above have been made.",
    )
    IPN_ORDER_COSTS = models.TextField(
        blank=True,
        null=True,
        help_text="Array with costs for each \
                                       product from order (expressed in \
                                       order's currency)",
    )
    IPN_REC_CURRENT_ITERATION_NO = models.TextField(
        blank=True,
        null=True,
        help_text="Current recurring \
                                                                  period (avaible \
                                                                  only for recurrent \
                                                                  payments)",
    )
    IPN_REC_ORIGINAL_REFNO = models.TextField(
        blank=True,
        null=True,
        help_text="Array containing \
                                                            the reference to \
                                                            the original order",
    )
    IPN_REC_INTERVAL = models.TextField(
        blank=True,
        null=True,
        help_text="Array containing \
                                                      recurring intervals \
                                                      (day/month/week) for \
                                                      each order",
    )
    IPN_REC_EXPIRATION_DATE = models.TextField(
        blank=True,
        null=True,
        help_text="Array with \
                                                             expiration dates \
                                                             for each \
                                                             recurrence",
    )
    IPN_REC_MULTIPLIER = models.TextField(
        blank=True,
        null=True,
        help_text="Array with reccurence \
                                                        period (interval x \
                                                        multiplier) for each \
                                                        product from the order",
    )
    IPN_DELIVEREDCODES = models.TextField(
        blank=True,
        null=True,
        help_text="Array with the codes \
                                                        delivered to the \
                                                        clients, if the PayU \
                                                        contract contains this \
                                                        feature. Each element \
                                                        in the array is \
                                                        represented by a \
                                                        string, having comma \
                                                        (,) as a separator for \
                                                        each sent code, in \
                                                        case the ordered \
                                                        quantity is greater \
                                                        than 1.",
    )
    IPN_DOWNLOAD_LINK = models.CharField(
        blank=True,
        null=True,
        max_length=250,
        help_text="Download link of the \
                                                       product delivered to \
                                                       the client",
    )
    IPN_TOTAL = models.TextField(
        blank=True,
        null=True,
        help_text="Partial total on order line \
                                               (including VAT), with \
                                               period/full-stop (.) as a \
                                               decimal place separator",
    )
    IPN_TOTALGENERAL = models.CharField(
        blank=True,
        null=True,
        max_length=40,
        help_text="Total transaction \
                                                      amount, including VAT \
                                                      costs, with \
                                                      period/full-stop (.) as \
                                                      a decimal place \
                                                      separator",
    )
    IPN_SHIPPING = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Total amount paid for \
                                                  shippment",
    )
    IPN_REFERRER = models.CharField(
        blank=True, null=True, max_length=250, help_text="HTTP referrer of the sale."
    )
    IPN_GLOBALDISCOUNT = models.CharField(
        blank=True,
        null=True,
        max_length=250,
        help_text="Global discount of the \
                                                        sale. This field is \
                                                        option and is avaible \
                                                        only if the amount is \
                                                        greater than zero.",
    )
    IPN_COMMISSION = models.CharField(
        blank=True,
        null=True,
        max_length=50,
        help_text="Payu's commision in RON, \
                                                    with period/full-stop (.) \
                                                    as a decimal place \
                                                    separator.",
    )
    IPN_DATE = models.DateTimeField(
        blank=True,
        null=True,
        max_length=40,
        help_text="IPN POST's sending date in the \
                                               following format: YmdHMS (ex.: \
                                               20120426145935)",
    )

    response = models.TextField(blank=True)
    ip_address = models.GenericIPAddressField(blank=True, null=True)

    flag = models.BooleanField(default=False)
    flag_info = models.TextField(blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def set_flag(self, info):
        """
        Sets a flag on the transaction and also sets a reason.
        """

        self.flag = True
        self.flag_info += info

    @property
    def is_authorized(self):
        return self.ORDERSTATUS in ["PAYMENT_AUTHORIZED", "PAYMENT_RECEIVED", "TEST"]

    @property
    def is_completed(self):
        return self.ORDERSTATUS == "COMPLETE"

    def __unicode__(self):
        return "<IPN: %s>" % self.REFNO

    class Meta:
        verbose_name = "PayU IPN"
        db_table = "payu_ipn"
        app_label = "payu"


class PayUIDN(models.Model):
    ipn = models.OneToOneField(PayUIPN, on_delete=models.CASCADE)
    sent = models.BooleanField(default=False)

    success = models.BooleanField(default=False)
    response = models.TextField(blank=True)

    class Meta:
        verbose_name = "PayU IDN"
        app_label = "payu"

    def send(self):
        payload = self._build_payload(PAYU_MERCHANT, PAYU_MERCHANT_KEY)

        try:
            response = requests.post(PAYU_IDN_URL, data=payload)

            self.success = response.status_code == 200
            self.response = response.content
        except Exception as e:
            self.response = str(e)
            self.success = False

        self.sent = True
        self.save()

    @classmethod
    def signature(cls, payload, merchant_key):
        hashable_fields = [
            "MERCHANT",
            "ORDER_REF",
            "ORDER_AMOUNT",
            "ORDER_CURRENCY",
            "IDN_DATE",
            "CHARGE_AMOUNT",
        ]

        hash_values = [payload[field] for field in hashable_fields if field in payload]

        confirmation_hash = "".join(
            [
                "{length}{value}".format(
                    length=len(str(value).encode("utf-8")), value=value
                )
                for value in hash_values
            ]
        ).encode("utf-8")
        return hmac.new(merchant_key, confirmation_hash, hashlib.md5).hexdigest()

    def _build_payload(self, merchant, merchant_key, now=None):
        payload = OrderedDict(
            [
                ("MERCHANT", merchant),
                ("ORDER_REF", self.ipn.REFNO or -1),
                ("ORDER_AMOUNT", self.ipn.IPN_TOTALGENERAL or 0),
                ("ORDER_CURRENCY", self.ipn.CURRENCY or "RON"),
                (
                    "IDN_DATE",
                    now or datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
                ),
            ]
        )
        payload["ORDER_HASH"] = self.signature(payload, merchant_key)

        return payload

    def __unicode__(self):
        return "<IDN: %s>" % self.pk


class PayUToken(models.Model):
    ipn = models.OneToOneField(PayUIPN, on_delete=models.CASCADE)

    # used for token/v1 api payments (same value as IPN's REFNO)
    # DEPRECATED
    IPN_CC_TOKEN = models.CharField(
        max_length=9, verbose_name="Token (deprecated)", blank=True, null=True
    )

    # can be used for the `IPN_CC_TOKEN` field in alu/v3 token payments
    TOKEN_HASH = models.CharField(
        max_length=64, verbose_name="Token", blank=True, null=True
    )

    # documentation is unclear of the length and format of this field
    IPN_CC_MASK = models.CharField(max_length=36, verbose_name="Last 4 digits")

    IPN_CC_EXP_DATE = models.DateField(verbose_name="Expiration date")

    class Meta:
        verbose_name = "PayU Tokens"
        app_label = "payu"

    def __unicode__(self):
        return "<Token: %s>" % (self.TOKEN_HASH or self.IPN_CC_TOKEN)


@receiver(post_save, sender=PayUToken)
def post_payu_ipn_cc_token_save(sender, instance=None, **kwargs):
    alu_token_created.send(sender=instance)


@receiver(post_save, sender=PayUIPN)
def post_payu_ipn_save(sender, instance=None, **kwargs):
    if instance.flag:
        payment_flagged.send(sender=instance)
        return

    if instance.is_authorized:
        payment_authorized.send(sender=instance)

    if instance.is_completed:
        payment_completed.send(sender=instance)
