# By: Riasat Ullah
# This file contains all view that are related to subscriptions.

from billings import coupon_handler
from dbqueries import db_accounts, db_billings, db_organizations, db_users
from exceptions.user_exceptions import InvalidCoupon, InvalidRequest, UnauthorizedRequest
from modules import subscription_updater
from rest_framework.decorators import api_view
from rest_framework.response import Response
from translators import label_translator as _lt
from utils import constants, errors, info, logging, payment_vendors, permissions, times, tokenizer, var_names
from utils.db_connection import CACHE_CLIENT, CONN_POOL
from validations import request_validator
import configuration
import jwt


@api_view(['POST'])
def get_current_subscription(request, conn=None):
    '''
    Get the details of the current subscription that an organization has at a point in time.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> dict of details
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_permission, org_permission = tokenizer.authorize_request(request)
            subscription_details = db_accounts.get_current_subscription(conn, times.get_current_timestamp(), org_id)
            return Response(subscription_details)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def update_subscription(request, conn=None, cache=None):
    '''
    Add a new subscription. It will end the old subscription that was in place.
    :param request: Http request
    :param conn: db connection
    :param cache: cache client
    :return: Http response -> str
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.subscription_name]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            cache = CACHE_CLIENT if cache is None else cache
            request_validator.validate_fields(request, expected_fields)

            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            subscription_id = configuration.subscription_name_id_map[request.data[var_names.subscription_name]]
            current_time = times.get_current_timestamp()

            if permissions.is_user_owner(user_perm):
                curr_cards = db_accounts.get_cards(conn, current_time, org_id)

                if subscription_id != configuration.free_subscription_id and len(curr_cards) == 0:
                    return Response(_lt.get_label(errors.err_org_subscription_card_required, lang), status=412)

                subscription_updater.update_subscription(conn, cache, current_time, subscription_id, {org_id: org_perm})
                return Response(_lt.get_label(info.msg_account_subscription_updated, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, LookupError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_cards(request, conn=None):
    '''
    Get the cards that is associated to an account.
    :param request: Http request
    :param conn: db connection
    :return: Http response -> list of dict
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            if permissions.is_user_owner(user_perm):
                cards = db_accounts.get_cards(conn, times.get_current_timestamp(), org_id)
                return Response(cards)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def add_card(request, conn=None):
    '''
    Add a card to an account.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.customer_id, var_names.payment_method]
        optional_fields = [var_names.card_brand, var_names.card_last_four_digits]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields, optional_fields)

            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            customer_id = request.data[var_names.customer_id]
            payment_method = request.data[var_names.payment_method]

            vendor_tokens = {var_names.customer_id: customer_id, var_names.payment_method: payment_method}
            card_brand, last_four_digits = payment_vendors.get_stripe_card_brand_and_last_four(payment_method)
            current_time = times.get_current_timestamp()

            if permissions.is_user_owner(user_perm):
                # check if there is sufficient funds on the account or not
                try:
                    payment_vendors.create_stripe_payment_intent(customer_id, payment_method, constants.usd_curr,
                                                                 configuration.card_verification_hold_amount,
                                                                 place_hold=True)
                except Exception as e:
                    logging.exception(str(e))
                    return Response(_lt.get_label(str(e), lang), status=400)

                current_cards = db_accounts.get_cards(conn, current_time, org_id)
                default_card = True
                if len(current_cards) > 0:
                    default_card = False

                db_accounts.add_card(conn, current_time, org_id, card_brand,
                                     last_four_digits, vendor_tokens, default=default_card)

                return Response(_lt.get_label(info.msg_account_card_added, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def delete_card(request, conn=None):
    '''
    Delete a card from an account.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.card_id]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)

            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            card_id = request.data[var_names.card_id]

            if permissions.is_user_owner(user_perm):
                current_time = times.get_current_timestamp()
                curr_sub_id, is_trial = db_accounts.get_current_subscription_status(conn, current_time, org_id)
                curr_cards = db_accounts.get_cards(conn, current_time, org_id)

                if curr_sub_id != configuration.free_subscription_id and not is_trial:
                    if len(curr_cards) == 1:
                        return Response(_lt.get_label(errors.err_org_card_deletion_paid_account, lang), status=412)

                    # do not allow the default card to be deleted
                    for item in curr_cards:
                        if item[var_names.card_id] == card_id and item[var_names.default_card]:
                            return Response(_lt.get_label(errors.err_org_card_deletion_default, lang), status=412)

                db_accounts.delete_card(conn, current_time, org_id, card_id)

                return Response(_lt.get_label(info.msg_account_card_deleted, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def set_default_card(request, conn=None):
    '''
    Set a particular card as the default payment option for an organization.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.card_id]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)

            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            card_id = request.data[var_names.card_id]

            if permissions.is_user_owner(user_perm):
                current_time = times.get_current_timestamp()
                db_accounts.set_card_as_default(conn, current_time, org_id, card_id)
                return Response(_lt.get_label(info.msg_account_card_default_set, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, LookupError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_stripe_token(request, conn=None):
    '''
    Get vendor token.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            stripe_key = payment_vendors.get_stripe_token()
            customer_id = db_accounts.get_stripe_customer_id(conn, times.get_current_timestamp(), org_id)

            customer_id, client_secret = payment_vendors.get_stripe_card_setup_details(customer_id)

            return Response([stripe_key, customer_id, client_secret])
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_bills(request, conn=None):
    '''
    Get the bills of an organization.
    :param request: Http request
    :param conn: db connection
    :return: HttpResponse
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        optional_fields = [var_names.start_period, var_names.end_period]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, [], optional_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            start_period = times.get_date_from_string(request.data[var_names.start_period])\
                if var_names.start_period in request.data else None
            end_period = times.get_date_from_string(request.data[var_names.end_period])\
                if var_names.end_period in request.data else None

            if permissions.is_user_owner(user_perm):
                bills = db_billings.get_bills_list(conn, org_id, start_period, end_period)
                return Response(bills)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except InvalidRequest as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_bill_details(request, conn=None):
    '''
    Get the details of a specific bill.
    :param request: Http request
    :param conn: db connection
    :return: HttpResponse -> dict
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.bill_id]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.is_user_owner(user_perm):
                details = db_billings.get_bill_details(conn, org_id, request.data[var_names.bill_id])[0]
                return Response(details)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, LookupError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def update_billing_currency(request, conn=None):
    '''
    Update the billing currency of the organization.
    :param request: Http request
    :param conn: db conn
    :return: HttpResponse
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.billing_currency]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.is_user_owner(user_perm):
                db_organizations.update_billing_currency(conn, times.get_current_timestamp(), org_id,
                                                         request.data[var_names.billing_currency])
                return Response(_lt.get_label(info.msg_account_billing_currency_updated, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except (InvalidRequest, LookupError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(str(e), lang), status=400)
        except (UnauthorizedRequest, jwt.ExpiredSignatureError, jwt.InvalidSignatureError) as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_authorization, lang), status=401)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_billing_information(request, conn=None):
    '''
    Get the optional billing information of an organization.
    :param request: Http request
    :param conn: db conn
    :return: HttpResponse -> dict -> {vat_id: , additional_emails: , billing_address: }
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.is_user_owner(user_perm):
                data = db_billings.get_optional_billing_info(conn, times.get_current_timestamp(), org_id)
                return Response(data)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def update_billing_information(request, conn=None):
    '''
    Update the optional billing information of an organization.
    :param request: Http request
    :param conn: db conn
    :return: HttpResponse
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.vat_id, var_names.additional_emails, var_names.billing_address]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.is_user_owner(user_perm):
                db_billings.update_optional_billing_info(
                    conn, times.get_current_timestamp(), org_id, request.data[var_names.vat_id],
                    request.data[var_names.additional_emails], request.data[var_names.billing_address]
                )
                return Response(_lt.get_label(info.msg_org_billing_information_updated, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_account_owner(request, conn=None):
    '''
    Get the display name and user_id of the account owner.
    :param request: Http request
    :param conn: db conn
    :return: HttpResponse -> list -> [display name, user_id]
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)

            if permissions.is_user_owner(user_perm):
                display_name = db_users.get_user_display_name(conn, times.get_current_timestamp(),
                                                              org_id, user_id=user_id)
                data_to_send = [display_name, user_id]
                return Response(data_to_send)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def save_onboarding_answers(request, conn=None):
    '''
    Save the answers of the welcome questionnaire provided by the owner of the organization at the time of onboarding.
    :param request: Http request
    :param conn: db conn
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.answers]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            if permissions.is_user_owner(user_perm):
                db_accounts.save_onboarding_answers(conn, times.get_current_timestamp(), org_id,
                                                    request.data[var_names.answers])
                return Response(_lt.get_label(info.msg_success, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def get_active_credits(request, conn=None):
    '''
    Get all the active credits an organization has at the moment.
    :param request: Http request
    :param conn: db connection
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, [])
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            if permissions.is_user_owner(user_perm):
                org_credits = db_billings.get_organization_credits(conn, times.get_current_timestamp(), org_id,
                                                                   active=True, for_display=True)
                return Response(org_credits)
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)


@api_view(['POST'])
def redeem_credit(request, conn=None):
    '''
    Redeem credit with a coupon.
    :param request: Http request
    :param conn: db conn
    :return: Http response
    '''
    if request.method == 'POST':
        lang = request_validator.get_user_language(request)
        expected_fields = [var_names.coupon]
        try:
            conn = CONN_POOL.get_db_conn() if conn is None else conn
            request_validator.validate_fields(request, expected_fields)
            user_id, org_id, user_perm, org_perm = tokenizer.authorize_request(request)
            if permissions.is_user_owner(user_perm):
                current_time = times.get_current_timestamp()
                curr_cards = db_accounts.get_cards(conn, current_time, org_id)
                if len(curr_cards) == 0:
                    return Response(_lt.get_label(errors.err_org_redemption_card_required, lang), status=412)
                else:
                    coupon_handler.redeem_coupon(conn, current_time, request.data[var_names.coupon], org_id)
                    return Response(_lt.get_label(info.msg_success, lang))
            else:
                return Response(_lt.get_label(errors.err_user_rights, lang), status=403)
        except InvalidCoupon:
            logging.exception(errors.err_org_coupon_invalid)
            return Response(_lt.get_label(errors.err_org_coupon_invalid, lang), status=400)
        except Exception as e:
            logging.exception(str(e))
            return Response(_lt.get_label(errors.err_system_error, lang), status=500)
        finally:
            CONN_POOL.put_db_conn(conn)
