import requests
import settings
import logging
import json

from utils import create_signature, verify_signature, encrypt_vpos_configuration, encrypt_data
from configuration import Configuration
from client_exceptions import ConfigurationError, ApiValidationError
from decimal import Decimal

logger = logging.getLogger(__name__)


class BKMExpress(object):
    def __init__(self, merchant_id, merchant_secret, environment='SANDBOX'):
        self.environment = environment
        self.merchant_id = merchant_id
        self.merchant_secret = merchant_secret
        self._config = None
        self._connection_token = None

    @property
    def config(self):
        if not self._config:
            if not self.merchant_id:
                raise ConfigurationError("Merchant id can not be NULL or Empty.")
            if not self.merchant_secret:
                raise ConfigurationError("Merchant Private Key can not be NULL or Empty.")

            self._config = Configuration(self.environment, self.merchant_id, self.merchant_secret)

        return self._config

    @property
    def connection_token(self):
        return self.generate_connection_token()

    def generate_connection_token(self):
        """
        This method returns connection token response
        which will be use like bridge between BKM and API
        from BKM
        :return:

        """
        merchant_id = self.config.merchant_id
        private_key = self.config.merchant_private_key
        signature = create_signature(data=merchant_id, private_key=private_key)

        payload = {
            'id': merchant_id,
            'signature': signature
        }

        try:
            request_url = self.config.baseUrl + "merchant/login"
            response = self.send_request(request_url, query=payload)
            return response

        except Exception as e:
            raise ConfigurationError(e)

    def create_one_time_ticket(self, amount,
                               installment_url, nonce_url, order_id,
                               campaign_code=None, tckn=None, msisdn=None,
                               address=None, agreement_url=None):
        """
        :param agreement_url: - optional
        :param address: - optional
        :param msisdn: customer mobile phone - optional
        :param tckn: if sale data include tckn - optional
        :param campaign_code: AccountProductBkm.campaign_code - optional
        :param connection_token: generated by connection token function
        :param amount:
        :param installment_url: check installment
        :param nonce_url: check sales property
        :param order_id:

        :return:
        """

        try:
            connection_token_dict = self.connection_token
            if self.validate_parameters(connection_token_dict, amount,
                                        installment_url, nonce_url, order_id):
                payload = {
                    "amount": str(amount).replace('.', ','),
                    "installmentUrl": installment_url,
                    "nonceUrl": nonce_url,
                    "orderId": order_id,
                }
                connection_token_data = connection_token_dict.get('data')
                connection_id = connection_token_data.get('path')
                connection_token = connection_token_data.get('token')
                payload = self.update_payload(payload, campaign_code,
                                              tckn, msisdn, address, agreement_url)

                url = self.config.baseUrl + "merchant/" + connection_id + "/ticket?type=payment"

                response = self.send_request(url,
                                             header={'Bex-Connection': connection_token},
                                             query=payload)
                return response

        except ValueError:
            logger.exception("[BKM Express] Invalid Parameter(s)")
            raise ValueError("[BKM Express] Invalid Parameter(s)")
        except Exception:
            logger.exception("[BKM Express] Your request could not send")
            raise Exception("[BKM Express] Your request could not send")

    @staticmethod
    def send_request(url, query, header={}):
        """
        Returns json response according to defined endpoint
        :param url:
        :param query:
        :param token:
        :return:
        """
        header.update({'Content-type': 'application/json'})

        response = requests.post(url=url, json=query,
                                 headers=header,
                                 verify=settings.SSL_VERIFICATION)
        if not response.ok:
            raise Exception(query, response.json())

        return response.json()

    @staticmethod
    def validate_parameters(connection_token, amount, installment_url, nonce_url, order_id):

        if not connection_token:
            raise ApiValidationError("Connection token must be given")
        elif not amount:
            raise ApiValidationError("Invalid amount")
        elif not installment_url:
            raise ApiValidationError("Installment URL must be given")
        elif not nonce_url:
            raise ApiValidationError("Nonce URL must be given")
        elif not order_id:
            raise ApiValidationError("Invalid order id")

        return True

    @staticmethod
    def update_payload(payload, campaign_code, tckn, msisdn, address, agreement_url):
        # Validations may add

        if campaign_code:
            payload["campaignCode"] = campaign_code
        if tckn:
            payload["tckn"] = {'check': True,
                               'no': encrypt_data(str(tckn))}
        if msisdn:
            payload["msisdn"] = {'check': True,
                                 'no': encrypt_data(str(msisdn))}
        if address:
            payload["address"] = address
        if agreement_url:
            payload["agreementUrl"] = agreement_url

        return payload

    @staticmethod
    def installment(ticket_id, bin_number, signature, installments):

        if verify_signature(ticket_id=ticket_id, signature=signature):
            installment_response = []
            for installment, amount in installments.iteritems():
                installment = int(installment)
                installment_amount = "%.2f" % (amount / installment)

                # todo write replace method
                output = {
                    'numberOfInstallment': str(installment),
                    'installmentAmount': str(installment_amount).replace('.', ','),
                    'totalAmount': str("%.2f" % amount).replace('.', ','),
                    'vposConfig': '',
                    'label': ''
                }

                installment_response.append(output)

            return {bin_number: installment_response}

    def configure_vpos(self, bank_code, user_id,
                       password, service_url,
                       order_id=None, **kwargs):

        bank_code = settings.BANK_CODE_MAP.get(bank_code)

        if self.config.environment == 'PRODUCTION':
            vpos_configuration = {
                'vposUserId': user_id,
                'vposPassword': password,
                'bankIndicator': bank_code,
                'serviceUrl': service_url
            }

            if bank_code in ['0046', '0111', '0012', '0064', '0010']:

                extra_data = {
                    'extra': {
                        'ClientId': kwargs.get('client_id'),
                        'storekey': kwargs.get('storekey')
                    },
                }
                vpos_configuration.update(extra_data)

            elif bank_code == '0015':
                extra_data = {
                    'extra': {
                        'posno': kwargs.get('pos_no'),
                        'uyeno': kwargs.get('uye_no'),
                        'islemyeri': kwargs.get('islemyeri'),
                        'uyeref': kwargs.get('client_ref'),
                    },
                }
                vpos_configuration.update(extra_data)

            elif bank_code == '0067':
                extra_data = {
                    'extra': {
                        'mid': kwargs.get('merchant_id'),
                        'tid': kwargs.get('terminal_id'),
                        'posnetID': kwargs.get('islemyeri'),
                    },
                }
                vpos_configuration.update(extra_data)

        else:
            vpos_configuration = settings.VPOS_TEST_CONFS.get(bank_code)
        if order_id:
            vpos_configuration['extra']['overrideOrderId'] = order_id

        vpos_configuration = json.dumps(vpos_configuration).replace(' ', '')
        return encrypt_vpos_configuration(vpos_configuration)

    def send_nonce_response(self):

        data = {
            'result': 'ok',
            'data': 'ok',
        }

        header = {
            'Status': '202 Accepted',
            'Content-Length': len(json.dumps(data)),
            'Connection': 'close',
        }

        return data, header

    def send_nonce_request(self, amount,
                           result, message,
                           data={}):

        connection_token_dict = self.connection_token.get('data')
        connection_id = connection_token_dict.get('path')
        connection_token = connection_token_dict.get('token')
        ticket_id = data.get('path')
        signature = data.get('signature')
        nonce_token = data.get('token')

        request_url = self.config.baseUrl + "merchant/" + \
                      connection_id + "/ticket/" + \
                      ticket_id + "/operate?name=commit"

        header = {
            'Bex-Connection': connection_token,
            'Bex-Nonce': nonce_token,
        }

        request_amount = data.get('reply').get('totalAmount')
        request_amount = Decimal(request_amount.replace(',', '.'))

        payload = {
            'result': result,
            'nonce': nonce_token,
            'id': data.get('path'),
            'message': message
        }

        if result:
            try:
                if verify_signature(ticket_id=data.get('id'), signature=signature):
                    payload['result'] = True
                    payload['message'] = 'succeeded',

                    if amount != request_amount:
                        logger.exception("[BKM Express] Nonce request amount mismatch ")
                        payload['result'] = False
                        payload['message'] = 'Amount could not match',

            except Exception:
                logger.exception("[BKM Express] Nonce request signature verification failed ")
                payload['result'] = False
                payload['message'] = 'Signature verification failed',

        return payload, self.send_request(request_url, payload, header)
