import binascii
import json

import requests
from decouple import config, Csv
from flask import Flask, request
from flask_restful import Resource, Api
from pyotp import TOTP

from config import etcd_client as client, logging, APP_PORT
from stripe_utils import StripeUtils
from uuid import uuid4

app = Flask(__name__)
api = Api(app)


def check_otp(name, realm, token):
    try:
        data = {
            "auth_name": config("AUTH_NAME", ""),
            "auth_token": TOTP(config("AUTH_SEED", "")).now(),
            "auth_realm": config("AUTH_REALM", ""),
            "name": name,
            "realm": realm,
            "token": token,
        }
    except binascii.Error:
        return 400

    response = requests.post(
        "{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
            OTP_SERVER=config("OTP_SERVER", ""),
            OTP_VERIFY_ENDPOINT=config("OTP_VERIFY_ENDPOINT", "verify/"),
        ),
        data=data,
    )
    return response.status_code


class ListProducts(Resource):
    @staticmethod
    def get():
        products = client.get_prefix("/v1/products/", value_in_json=False)
        prod_dict = {}
        for p in products:
            prod_dict[p.key] = json.loads(p.value)
        logging.debug("Products = {}".format(prod_dict))
        return prod_dict, 200


class AddProduct(Resource):
    @staticmethod
    def post():
        data = request.json
        logging.debug("Got data: {}".format(str(data)))
        REALM_ALLOWED = config("REALM_ALLOWED", cast=Csv(str))
        logging.debug("REALM_ALLOWED = {}".format(REALM_ALLOWED))
        if data["realm"] not in REALM_ALLOWED:
            logging.error(
                "The given realm {} is not "
                "allowed to do add product".format(data["realm"]))
            return {"message": "Forbidden"}, 403
        otp_response = check_otp(data["name"], data["realm"],
                                 data["token"])
        if otp_response != 200:
            return {"message": "Wrong Credentials"}, 403

        try:
            product_uuid = uuid4().hex
            product_key = "/v1/products/{}".format(product_uuid)
            product_value = {
                "name": data["product_name"],
                "description": data["product_description"],
                "type": data["product_type"],
                "price": data["product_price"],
                "recurring_duration": data["product_recurring_duration"],
                "recurring_duration_units":
                    data["product_recurring_duration_units"]
            }
            logging.debug("Adding product data: {}".format(str(product_value)))
            client.put(product_key, product_value, value_in_json=True)
            return {"message":
                        "Product {} created".format(data['product_name'])}, 200
        except KeyError as ke:
            logging.error("KeyError occurred. details = {}".format(str(ke)))
            return {"message":
                        "Missing or wrong parameters"}, 400


class UserRegisterPayment(Resource):

    @staticmethod
    def get_token(card_number, cvc, exp_month, exp_year):
        stripe_utils = StripeUtils()
        token_response = stripe_utils.get_token_from_card(
            card_number, cvc, exp_month, exp_year
        )
        if token_response["response_object"]:
            return token_response["response_object"].id
        else:
            return None

    @staticmethod
    def post():
        try:
            data = request.json
            logging.debug("Got data: {}".format(str(data)))
            otp_response = check_otp(data["name"], data["realm"],
                                     data["token"])
            last4 = data['card_number'].strip()[-4:]
            if otp_response != 200:
                return {"message": "Wrong Credentials"}, 403

            stripe_utils = StripeUtils()

            # Does customer already exist ?
            stripe_customer = stripe_utils.get_stripe_customer_from_email(
                data["email"])

            # Does customer already exist ?
            if stripe_customer is not None:
                logging.debug(
                    "Customer {} exists already".format(data['email'])
                )
                # Check if the card already exists
                ce_response =  stripe_utils.card_exists(
                        stripe_customer.id, cc_number=data["card_number"],
                        exp_month=int(data["expiry_month"]),
                        exp_year=int(data["expiry_year"]),
                        cvc=data["cvc"])
                if ce_response["response_object"]:
                    message = ("The given card ending in "
                               "{} exists already.").format(last4)
                    logging.debug(message)
                    return { "message": message }, 400
                elif ce_response["response_object"] is False:
                    # Associate card with user
                    logging.debug("Adding card ending in {}".format(last4))
                    token_response = stripe_utils.get_token_from_card(
                        data["card_number"], data["cvc"], data["expiry_month"],
                        data["expiry_year"]
                    )
                    if token_response["response_object"]:
                        logging.debug(
                            "Token {}".format(
                                token_response["response_object"].id
                            )
                        )
                        resp = stripe_utils.associate_customer_card(
                            stripe_customer.id,
                            token_response["response_object"].id
                        )
                        if resp["response_object"]:
                            return {"message":
                                "Card ending in {} registered as your payment "
                                "source".format(last4)
                            }, 200
                    else:
                        logging.error("Could not obtain token")
                        return {"message": "Error with payment gateway. "
                                           "Contact support"}, 400
                else:
                    logging.error(
                        "Error occurred {}".format(ce_response["error"])
                    )
                    return {"message": "Error: {}".format(
                        ce_response["error"]
                    )}, 400
            else:
                # Stripe customer does not exist, create a new one
                logging.debug(
                    "Customer {} does not exist, "
                    "creating new".format(data['email'])
                )
                token_response = stripe_utils.get_token_from_card(
                    data["card_number"], data["cvc"], data["expiry_month"],
                    data["expiry_year"]
                )
                if token_response["response_object"]:
                    logging.debug(
                        "Token {}".format(
                            token_response["response_object"].id))

                    #Create stripe customer
                    stripe_customer_resp = stripe_utils.create_customer(
                        name=data["card_holder_name"],
                        token=token_response["response_object"].id,
                        email=data["email"]
                    )
                    if stripe_customer_resp["response_object"]:
                        logging.debug(
                            "Created stripe customer {}".format(
                                stripe_customer_resp["response_object"].id
                            )
                        )
                        stripe_customer = stripe_customer_resp[
                            "response_object"]
                        return {"message":
                                    "Card ending in {} registered as your payment "
                                    "source".format(last4)
                                }, 200
                    else:
                        logging.error("Could not get/create stripe_customer "
                                      "for {}".format(data["email"]))
                        return {"message":
                                    "Error with card. Contact support"}, 400
                else:
                    logging.error("Could not obtain token")
                    return {"message": "Error with payment gateway. "
                                       "Contact support"}, 400
        except KeyError as key_error:
            logging.error("Key error occurred")
            logging.error(str(key_error))
            return {"message": "Missing or wrong parameters"}, 400


api.add_resource(ListProducts, "/product/list")
api.add_resource(AddProduct, "/product/add")
api.add_resource(UserRegisterPayment, "/user/register_payment")

if __name__ == '__main__':
    app.run(host="::", port=APP_PORT, debug=True)