import base64
import datetime
import logging

import requests
from Crypto.Cipher import DES3

from azbankgateways.banks import BaseBank
from azbankgateways.exceptions import BankGatewayConnectionError, SettingDoesNotExist
from azbankgateways.exceptions.exceptions import (
    BankGatewayRejectPayment,
    BankGatewayStateInvalid,
)
from azbankgateways.models import BankType, CurrencyEnum, PaymentStatus
from azbankgateways.utils import get_json


class BMI(BaseBank):
    _merchant_code = None
    _terminal_code = None
    _secret_key = None

    def __init__(self, **kwargs):
        super(BMI, self).__init__(**kwargs)
        self.set_gateway_currency(CurrencyEnum.IRR)
        self._token_api_url = "https://sadad.shaparak.ir/vpg/api/v0/Request/PaymentRequest"
        self._payment_url = "https://sadad.shaparak.ir/VPG/Purchase"
        self._verify_api_url = "https://sadad.shaparak.ir/vpg/api/v0/Advice/Verify"

    def get_bank_type(self):
        return BankType.BMI

    def set_default_settings(self):
        for item in ["MERCHANT_CODE", "TERMINAL_CODE", "SECRET_KEY"]:
            if item not in self.default_setting_kwargs:
                raise SettingDoesNotExist()
            setattr(self, f"_{item.lower()}", self.default_setting_kwargs[item])

    def get_pay_data(self):
        time_now = datetime.datetime.now().strftime("%m/%d/%Y %H:%M:%S %p")
        data = {
            "TerminalId": self._terminal_code,
            "MerchantId": self._merchant_code,
            "Amount": self.get_gateway_amount(),
            "SignData": self._encrypt_des3(
                "{};{};{}".format(
                    self._terminal_code,
                    self.get_tracking_code(),
                    self.get_gateway_amount(),
                )
            ),
            "ReturnUrl": self._get_gateway_callback_url(),
            "LocalDateTime": time_now,
            "OrderId": self.get_tracking_code(),
            "AdditionalData": "oi:%s-ou:%s" % (self.get_tracking_code(), self.get_mobile_number()),
        }
        return data

    def prepare_pay(self):
        super(BMI, self).prepare_pay()

    def pay(self):
        super(BMI, self).pay()
        data = self.get_pay_data()
        response_json = self._send_data(self._token_api_url, data)
        if response_json["ResCode"] == "0":
            token = response_json["Token"]
            self._set_reference_number(token)
        else:
            logging.critical("BMI gateway reject payment")
            raise BankGatewayRejectPayment(self.get_transaction_status_text())

    """
    : gateway
    """

    def _get_gateway_payment_method_parameter(self):
        return "GET"

    def _get_gateway_payment_url_parameter(self):
        return self._payment_url

    def _get_gateway_payment_parameter(self):
        params = {"Token": self.get_reference_number()}
        return params

    def get_verify_data(self):
        super(BMI, self).get_verify_data()
        data = {
            "Token": self.get_reference_number(),
            "SignData": self._encrypt_des3(self.get_reference_number()),
        }
        return data

    def prepare_verify(self, tracking_code):
        super(BMI, self).prepare_verify(tracking_code)

    def verify(self, transaction_code):
        super(BMI, self).verify(transaction_code)
        data = self.get_verify_data()
        response_json = self._send_data(self._verify_api_url, data)
        if response_json["ResCode"] == "0":
            self._set_payment_status(PaymentStatus.COMPLETE)
            extra_information = (
                f"RetrivalRefNo={response_json['RetrivalRefNo']},SystemTraceNo={response_json['SystemTraceNo']}"
            )
            self._bank.extra_information = extra_information
            self._bank.save()
        else:
            self._set_payment_status(PaymentStatus.CANCEL_BY_USER)
            logging.debug("BMI gateway unapprove payment")

    def prepare_verify_from_gateway(self):
        super(BMI, self).prepare_verify_from_gateway()
        request = self.get_request()
        for method in ["POST", "GET", "data", "PUT"]:
            token = getattr(request, method, {}).get("token", None)
            if token:
                break
        if not token:
            raise BankGatewayStateInvalid
        self._set_reference_number(token)
        self._set_bank_record()

    def verify_from_gateway(self, request):
        super(BMI, self).verify_from_gateway(request)

    @classmethod
    def _pad(cls, text, pad_size=16):
        text_length = len(text)
        last_block_size = text_length % pad_size
        remaining_space = pad_size - last_block_size
        text = text + (remaining_space * chr(remaining_space))
        return text

    def _encrypt_des3(self, text):
        secret_key_bytes = base64.b64decode(self._secret_key)
        text = self._pad(text, 8)
        cipher = DES3.new(secret_key_bytes, DES3.MODE_ECB)
        cipher_text = cipher.encrypt(str.encode(text))
        return base64.b64encode(cipher_text).decode("utf-8")

    def _send_data(self, api, data):
        try:
            response = requests.post(api, json=data, timeout=5)
        except requests.Timeout:
            logging.exception("BMI time out gateway {}".format(data))
            raise BankGatewayConnectionError()
        except requests.ConnectionError:
            logging.exception("BMI time out gateway {}".format(data))
            raise BankGatewayConnectionError()

        response_json = get_json(response)
        self._set_transaction_status_text(response_json["Description"])
        return response_json
