import logging

from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from backend.authentication import JWTStatelessUserAuthentication, JWTOrAPIKeyAuthentication, APIKeyAuthentication

from app.api import api
from app.api.response_builder import ResponseBuilder
from app.loan_application.loan_application import LoanApplication

from app.user.authentication import Authentication
from app.user.user import User
from app.bank.user_bank import UserBank
from app.user.user_document import UserDocument
from app.loan_offer.loan_offer import LoanOffer
from app.util import util

log = logging.getLogger(__name__)


def validate_account_creation_inputs(
        is_merchant, phone_number, email, first_name, last_name, merchant_id, center_id, product_id, loan_amount,
        gender, pincode):
    if not (is_merchant is not None and phone_number and email and first_name and last_name and merchant_id and
            product_id and loan_amount and gender in User.VALID_GENDERS and pincode):
        return api.INVALID_INPUT

    return api.SUCCESS


@api_view(['POST'])
def create_account(request):
    data = request.data
    response_builder = ResponseBuilder()

    phone_number = data.get('phone_number')
    email = data.get('email')
    first_name = data.get('first_name')
    last_name = data.get('last_name')
    merchant_id = data.get('merchant_id')
    center_id = data.get('center_id')
    product_id = data.get('product_id')
    loan_amount = data.get('loan_amount')
    pincode = data.get('pincode')
    gender = data.get('gender')
    is_merchant = data.get('is_merchant')

    status = validate_account_creation_inputs(
        is_merchant, phone_number, email, first_name, last_name, merchant_id, center_id, product_id, loan_amount,
        gender, pincode)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    status, application_id = User.create(
        phone_number, email, first_name, last_name, merchant_id, center_id, product_id, loan_amount, gender, pincode)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    loan_application = LoanApplication.get_loan_application(
        LoanApplication.get_application_id_from_hash(application_id))

    loan_application_object = LoanApplication(loan_application)
    user = loan_application_object.get_user()

    result = {"application_id": application_id}

    if is_merchant == 0:
        status, result["token"] = User(
            user).get_jwt_token_for_onboarding(loan_application_object)

    if util.is_status_failed(status):
        return response_builder.user_unauthorized_401(status)

    return response_builder.success().accepted_202().message('User created').result_object(result).get_response()


def validate_generate_otp_inputs(phone_number):
    if not phone_number:
        return api.INVALID_INPUT

    return api.SUCCESS


@api_view(['POST'])
@authentication_classes((JWTStatelessUserAuthentication,))
def generate_otp(request):
    data = request.data
    bearer_token = api.get_bearer_token(request)
    response_builder = ResponseBuilder()

    phone_number = data.get('phone_number')

    status = validate_generate_otp_inputs(phone_number)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    status = Authentication.generate_otp(phone_number, bearer_token)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    return response_builder.success().ok_200().message('OTP Generated').get_response()


def validate_verify_otp_inputs(phone_number, otp):
    if not (phone_number and otp):
        return api.INVALID_INPUT

    return api.SUCCESS


def verify_otp_api(data, bearer_token):
    phone_number = data.get('phone_number')
    otp = data.get('otp')

    status = validate_verify_otp_inputs(phone_number, otp)
    if util.is_status_failed(status):
        return status, None

    status, token = Authentication.validate_otp(
        phone_number, otp, bearer_token)
    if util.is_status_failed(status):
        return status, None

    result = {
        "token": token
    }

    return api.SUCCESS, result


@api_view(['POST'])
@authentication_classes((JWTStatelessUserAuthentication,))
def verify_otp(request):
    data = request.data
    bearer_token = api.get_bearer_token(request)
    response_builder = ResponseBuilder()

    status, result = verify_otp_api(data, bearer_token)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    return response_builder.success().ok_200().message('OTP verified').result_object(result).get_response()


def validate_validate_pan_inputs(pan, dob, loan_application_id):
    if not (pan and dob and loan_application_id):
        return api.INVALID_INPUT, None

    loan_application = LoanApplication.get_loan_application(
        hashed_loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def validate_pan(request):
    data = request.data
    response_builder = ResponseBuilder()

    loan_application_id = data.get('application_id')
    pan = data.get('pan')
    dob = data.get('dob')

    status, loan_application = validate_validate_pan_inputs(
        pan, dob, loan_application_id)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    status, response = User.validate_pan(pan)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    status = LoanApplication(loan_application).update_pan_details(pan, dob, response['name'])
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    return response_builder.success().ok_200().message('Valid pan').get_response()


def validate_upload_document_inputs(
        loan_application_id, document_name, document_format, document_content, document_password):
    log.debug(
        f"{loan_application_id}, {document_name}, {document_format}, {document_password}, {len(document_content)}")
    if not (loan_application_id and document_name and document_format and document_password is not None and
            document_content):
        return api.INVALID_INPUT

    return api.SUCCESS


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def upload_document(request):
    data = request.data
    bearer_token = api.get_bearer_token(request)
    client_id, client_secret = api.get_client_key_secret(request)

    response_builder = ResponseBuilder()

    loan_application_id = data.get('application_id', None)

    document_name = data.get('document_name', None)
    document_format = data.get('document_format', None)
    document_content = data.get('document_content', None)
    document_password = data.get('document_password', "")

    status = validate_upload_document_inputs(
        loan_application_id, document_name, document_format, document_content, document_password)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    status = UserDocument.upload_document(
        loan_application_id, document_name, document_format, document_content, document_password, bearer_token, client_id, client_secret)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    return response_builder.success().ok_200().message('Document uploaded').get_response()


def validate_update_address_inputs(loan_application_id, current_address, permanent_address):
    if not loan_application_id:
        return api.INVALID_INPUT, None

    if permanent_address and not (
            permanent_address.get("pincode") and permanent_address.get("city") and permanent_address.get("state")):
        return api.INVALID_INPUT, None

    if current_address and not (
            current_address.get("pincode") and current_address.get("city") and current_address.get("state")):
        return api.INVALID_INPUT, None

    if not (current_address or permanent_address):
        return api.INVALID_INPUT, None

    loan_application = LoanApplication.get_loan_application(
        hashed_loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def update_address(request):
    data = request.data
    response_builder = ResponseBuilder()

    loan_application_id = data.get('application_id')
    current_address = data.get("current_address")
    permanent_address = data.get("permanent_address")

    status, loan_application = validate_update_address_inputs(
        loan_application_id, current_address, permanent_address)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    if current_address:
        status = LoanApplication(loan_application).add_current_address(
            current_address['address_line_1'], current_address['address_line_2'], current_address['pincode'],
            current_address['city'], current_address['state'])
        if util.is_status_failed(status):
            return response_builder.get_ok200_fail_response(status)

    if permanent_address:
        status = LoanApplication(loan_application).add_permanent_address(
            permanent_address['address_line_1'], permanent_address['address_line_2'], permanent_address['pincode'],
            permanent_address['city'], permanent_address['state'])
        if util.is_status_failed(status):
            return response_builder.get_ok200_fail_response(status)

    return response_builder.success().ok_200().message('Address updated').get_response()


def validate_update_employment_details_inputs(loan_application_id, company_name, profession_type, salary):
    if not (loan_application_id and company_name and profession_type and salary):
        return api.INVALID_INPUT, None

    loan_application = LoanApplication.get_loan_application(
        hashed_loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def update_employment_details(request):
    data = request.data
    bearer_token = api.get_bearer_token(request)
    client_id, client_secret = api.get_client_key_secret(request)

    response_builder = ResponseBuilder()

    loan_application_id = data.get('application_id')

    company_name = data.get('company_name')
    profession_type = data.get('profession_type')
    salary = data.get('salary')

    status, loan_application = validate_update_employment_details_inputs(
        loan_application_id, company_name, profession_type, salary)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    status = LoanApplication(loan_application).update_employment_details(
        company_name, profession_type, salary, bearer_token, client_id, client_secret)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    return response_builder.success().ok_200().message('Employment details updated').get_response()


def validate_get_user_details_inputs(user, application_id):
    if not (user):
        return api.INVALID_INPUT, None

    if not application_id:
        loan_application = LoanApplication.get_loan_application_for_user(
            user.id)
        if not loan_application:
            return api.LOAN_APPLICATION_NOT_FOUND, None

        application_id = LoanApplication(
            loan_application).get_hashed_application_id()

    application = LoanApplication.get_loan_application(
        hashed_loan_application_id=application_id)
    if not application:
        return api.INVALID_INPUT, None

    return api.SUCCESS, application


@api_view(['GET'])
@authentication_classes((JWTStatelessUserAuthentication,))
@permission_classes((IsAuthenticated,))
def get_user_details(request):
    response_builder = ResponseBuilder()
    user = User.get_user(request.user.id)
    data = request.GET

    application_id = data.get('application_id')

    status, application = validate_get_user_details_inputs(
        user, application_id)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    application_details = LoanApplication(
        application).get_flow_application_details()

    user_details = User(user).get_user_details()
    _, applicable_offers = LoanOffer.get_applicable_offers(application)

    user_bank = UserBank.get_bank(LoanApplication(
        application).get_loan_application_id())
    if not user_bank:
        bank_details = {}
    else:
        bank_details = UserBank(user_bank).get_serialized_user_bank()

    documents = LoanApplication(application).get_list_of_uploaded_documents()

    result = {
        'details': user_details,
        'application_details': application_details,
        'applicable_offers': applicable_offers,
        'bank_details': bank_details,
        'is_mandate_setup': LoanApplication(application).get_mandate_setup_status(),
        'payment_status': LoanApplication(application).get_payment_status(),
        'documents': documents,
        'bank_statement_status': LoanApplication(application).get_bank_statement_status(),
    }

    return response_builder.success().ok_200().message('User details').result_object(result).get_response()


@api_view(['GET', 'POST'])
def get_pincode_details(request):
    response_builder = ResponseBuilder()
    data = request.GET
    solar_form = 0
    if not data:
        data = request.data
        solar_form = 1

    pincode = data.get('pincode')

    status, pincode_details = LoanApplication.get_pincode_details(pincode)
    if util.is_status_failed(status):
        if solar_form:
            return Response({'statusCode': 'CF300', 'status': 'OK', 'message': 'Pincode details could not be fetched'}, 200)
        else:
            return response_builder.get_ok200_fail_response(status)
    if solar_form:
        return Response({'statusCode': 'CF200', 'status': 'OK', 'data': pincode_details}, 200)
    else:
        return response_builder.success().ok_200().message('Pincode details').result_object(pincode_details).get_response()


def validate_bank_details_inputs(loan_application_id, account_number, ifsc_code, bank_name, branch_name, source=None):
    if not (loan_application_id and account_number and ifsc_code and bank_name and branch_name):
        return api.INVALID_INPUT, None
    if source != "no_touch":
        loan_application = LoanApplication.get_loan_application(hashed_loan_application_id=loan_application_id)
    else:
        loan_application = LoanApplication.get_loan_application(loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def update_bank_details(request):
    data = request.data
    response_builder = ResponseBuilder()

    loan_application_id = data.get('application_id')

    account_number = data.get('account_number')
    ifsc_code = data.get('ifsc_code').upper()
    bank_name = data.get('bank_name')
    branch_name = data.get('branch_name')
    source = data.get('source')
    status, loan_application = validate_bank_details_inputs(
        loan_application_id, account_number, ifsc_code, bank_name, branch_name, source)
    if util.is_status_failed(status):
        if source == "no_touch":
            return (response_builder.solar_fail().ok_200().message('Please pass all the required parameter')
                    .get_solar_response())
        else:
            return response_builder.get_bad_request_response(status)

    status = LoanApplication(loan_application).update_bank_details(
        account_number, bank_name, ifsc_code, branch_name)
    if util.is_status_failed(status):
        if source == "no_touch":
            return (response_builder.solar_fail().ok_200().message("Unable to verify Bank Account details")
                    .get_solar_response())
        else:
            return response_builder.get_bad_request_response(status)
    if source == "no_touch":
        return (response_builder.solar_success().ok_200().message('Bank updated')
                .get_solar_response())
    return response_builder.success().ok_200().message('Bank updated').get_response()


def validate_upload_aadhar_inputs(
        loan_application_id, front_document_name, front_document_format, front_document_content,
        front_document_password, back_document_name, back_document_format, back_document_content,
        back_document_password):
    if not (loan_application_id and front_document_name and front_document_format and
            front_document_content and front_document_password is not None):
        return api.INVALID_INPUT, None

    if back_document_name and not (back_document_format and back_document_content and
                                   back_document_password is not None):
        return api.INVALID_INPUT, None

    loan_application = LoanApplication.get_loan_application(
        hashed_loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def upload_address_proof(request):
    data = request.data
    response_builder = ResponseBuilder()

    hashed_loan_application_id = data.get('application_id')
    bearer_token = api.get_bearer_token(request)
    client_id, client_secret = api.get_client_key_secret(request)

    front_document_name = data.get('front_document_name', None)
    front_document_format = data.get('front_document_format', None)
    front_document_content = data.get('front_document_content', None)
    front_document_password = data.get('front_document_password', "")

    back_document_name = data.get('back_document_name', None)
    back_document_format = data.get('back_document_format', None)
    back_document_content = data.get('back_document_content', None)
    back_document_password = data.get('back_document_password', "")

    status, loan_application = validate_upload_aadhar_inputs(
        hashed_loan_application_id, front_document_name, front_document_format, front_document_content,
        front_document_password, back_document_name, back_document_format, back_document_content,
        back_document_password)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    if front_document_name == 'APPLICANT_AADHAAR_FRONT' and back_document_name == 'APPLICANT_AADHAAR_BACK':
        _, ocr_details = UserDocument.perform_aadhar_ocr(back_document_content)
    elif front_document_name == 'DRIVING_LICENSE':
        _, ocr_details = UserDocument.perform_driving_license_ocr(
            front_document_content)
    elif front_document_name == 'VOTAR_ID_FRONT' and back_document_name == 'VOTAR_ID_BACK':
        _, ocr_details = UserDocument.perform_voter_ocr(back_document_content)
    elif front_document_name == 'PASSPORT_FRONT' and back_document_name == 'PASSPORT_BACK':
        _, ocr_details = UserDocument.perform_passport_ocr(
            back_document_content)
    else:
        return response_builder.get_bad_request_response(api.INVALID_INPUT)

    if front_document_name:
        status = UserDocument.upload_document(
            hashed_loan_application_id, front_document_name, front_document_format, front_document_content,
            front_document_password, bearer_token, client_id, client_secret)
        if util.is_status_failed(status):
            return response_builder.get_ok200_fail_response(status)

    if back_document_name:
        status = UserDocument.upload_document(
            hashed_loan_application_id, back_document_name, back_document_format, back_document_content,
            back_document_password, bearer_token, client_id, client_secret)
        if util.is_status_failed(status):
            return response_builder.get_ok200_fail_response(status)

    result = {
        "ocr_details": ocr_details
    }

    return response_builder.success().ok_200().message('Aadhar uploaded').result_object(result).get_response()


def validate_aadhar_details_inputs(aadhar, loan_application_id):
    if not (aadhar and loan_application_id):
        return api.INVALID_INPUT, None

    loan_application = LoanApplication.get_loan_application(
        hashed_loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def generate_aadhar_otp(request):
    data = request.data
    response_builder = ResponseBuilder()

    aadhar = data.get('aadhar')
    loan_application_id = data.get('application_id')

    status, _ = validate_aadhar_details_inputs(aadhar, loan_application_id)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    status, access_key = User.generate_aadhar_otp(aadhar)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    result = {"access_key": access_key}
    return response_builder.success().ok_200().message('Aadhar otp generated').result_object(result).get_response()


def validate_aadhar_validate_otp_details_inputs(aadhar, otp, access_key, loan_application_id):
    if not (aadhar and loan_application_id and otp and access_key):
        return api.INVALID_INPUT, None

    loan_application = LoanApplication.get_loan_application(
        hashed_loan_application_id=loan_application_id)
    if not loan_application:
        return api.LOAN_APPLICATION_NOT_FOUND, None

    return api.SUCCESS, loan_application


@api_view(['POST'])
@authentication_classes((JWTOrAPIKeyAuthentication,))
@permission_classes((IsAuthenticated,))
def validate_aadhar_otp(request):
    data = request.data
    response_builder = ResponseBuilder()
    bearer_token = api.get_bearer_token(request)
    client_id, client_secret = api.get_client_key_secret(request)

    aadhar = data.get('aadhar')
    loan_application_id = data.get('application_id')
    access_key = data.get('access_key')
    otp = data.get('otp')

    status, loan_application = validate_aadhar_validate_otp_details_inputs(
        aadhar, otp, access_key, loan_application_id)
    if util.is_status_failed(status):
        return response_builder.get_bad_request_response(status)

    status, response = User.verify_aadhar_otp(aadhar, otp, access_key)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    status = LoanApplication(loan_application).update_aadhar_and_save_aadhar_docs(
        aadhar, response.get('address'), response.get('dob'), response.get('name'), response.get("share_code"),
        response.get("aadhar_zip"), response.get("aadhar_pdf_report"), bearer_token, client_id, client_secret)
    if util.is_status_failed(status):
        return response_builder.get_ok200_fail_response(status)

    result = {"address": response.get('address')}

    return response_builder.success().ok_200().message('Aadhar otp verified').result_object(result).get_response()
