# By: Riasat Ullah
# This file contains functions to help retrieve information about payment vendors.

from exceptions.user_exceptions import CardExpired, InsufficientFunds, PaymentSourceError
from taskcallrest import settings
from utils import errors, s3
import logging
import stripe


def get_stripe_token(secret_key=False):
    '''
    Gets the credentials to connect to Stripe.
    :return: str -> Stripe public key
    '''
    if settings.TEST_MODE:
        if secret_key:
            return 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
        return 'pk_test_TYooMQauvdEDq54NiTphI7jx'
    else:
        bucket = 'taskcall-prod-data'
        key = 'credentials/stripe_keys.json'
        str_public_key = 'public_key'
        str_secret_key = 'secret_key'
        try:
            data = s3.read_json(bucket, key)
            if secret_key:
                return data[str_secret_key]
            return data[str_public_key]
        except (OSError, IOError) as e:
            err = 'Could not read Stripe credentials file' + '\n' + str(e)
            raise OSError(err)


def get_stripe_card_setup_details(customer_id=None):
    '''
    Create a stripe customer
    :param customer_id: stripe customer_id
    :return: (str) customer id, client secret
    '''
    stripe.api_key = get_stripe_token(secret_key=True)

    if customer_id is None:
        customer = stripe.Customer.create()
        customer_id = customer['id']

    intent = stripe.SetupIntent.create(customer=customer_id)
    return customer_id, intent.client_secret


def detach_stripe_card(payment_method):
    '''
    Detach a payment method.
    :param payment_method: (str) stripe payment method ID
    '''
    stripe.api_key = get_stripe_token(secret_key=True)
    stripe.PaymentMethod.detach(payment_method)


def get_stripe_card_brand_and_last_four(payment_method, with_api_key=None):
    '''
    Get the brand and last 4 digits of a card
    :param payment_method: stripe payment method id
    :param with_api_key: the api key of TaskCall's account
    :return: (tuple) -> brand name, last 4 digits of card
    '''
    if with_api_key is None:
        with_api_key = get_stripe_token(secret_key=True)

    stripe.api_key = with_api_key

    try:
        details = stripe.PaymentMethod.retrieve(payment_method)
        return details['card']['brand'], details['card']['last4']
    except stripe.error.CardError as e:
        logging.exception(str(e))
        raise_stripe_card_error(e)
    except Exception as e:
        logging.exception(str(e))
        raise PaymentSourceError(errors.err_org_card_processing)


def create_stripe_payment_intent(customer_id, payment_method, currency, amount, with_api_key=None, place_hold=False):
    '''
    Create a charge for a stripe customer
    :param customer_id: the id of the customer
    :param payment_method: stripe payment method id
    :param currency: the currency to create the charge in
    :param amount: the amount to charge
    :param with_api_key: the api key of TaskCall's account
    :param place_hold: True if only a hold should be made on the card; otherwise False
    :return: (str) charge id (payment id) from stripe
    '''
    if with_api_key is None:
        with_api_key = get_stripe_token(secret_key=True)

    stripe.api_key = with_api_key

    # convert the amount to int with the last two digits representing cents
    amount = round(amount * 100)
    try:
        charge_object = stripe.PaymentIntent.create(
            amount=amount,
            currency=currency,
            customer=customer_id,
            payment_method=payment_method,
            off_session=True,
            confirm=True,
            capture_method='manual' if place_hold else 'automatic'
        )
        return charge_object['id']
    except stripe.error.CardError as e:
        logging.exception(str(e))
        raise_stripe_card_error(e)
    except Exception as e:
        logging.exception(str(e))
        raise PaymentSourceError(errors.err_org_card_processing)


def raise_stripe_card_error(exception):
    '''
    Identify the type of card error received from stripe and then raise the appropriate error.
    :param exception: exception details
    :errors: CardExpired, InsufficientFunds, PaymentSourceError
    '''
    if 'insufficient funds' in str(exception):
        raise InsufficientFunds(errors.err_org_card_insufficient_funds)
    elif 'expired' in str(exception):
        raise CardExpired(errors.err_org_card_expired)
    else:
        raise PaymentSourceError(errors.err_org_card_processing)
