import os, uuid, json, requests, hashlib, random, urllib.parse
from datetime import datetime

from lib import lockerjwt
from lib.commonlib import CommonLib
from lib.mongolib import Mongolib
from lib.rmq_parser import encode_json
from lib.redislib import RedisLib
from dotenv import load_dotenv
from flask import Flask, request, g
from flask_cors import CORS
from lib.rabbitmqlib import RabbitMQAccounts
from werkzeug.serving import WSGIRequestHandler

load_dotenv() 
from lib.validations import Validation
from lib import otp_service 
from lib import family_services
from lib import nominee_services 

otp_connector = otp_service.OTP_services() 
family_connector = family_services.Family_services()
nominee_connector = nominee_services.Nominee_services()

CommonLib = CommonLib()
MongoLib = Mongolib(os.getenv('MONGO_DB_ACCOUNTS'),
                    os.getenv('MONGO_URI_ACCOUNTS'))

allowed_origin = os.getenv('ALLOWED_ORIGIN') 
MAX_CONTENT_LENGTH = os.getenv('MAX_CONTENT_LENGTH')
app = Flask(__name__)
cors = CORS(app, origins=allowed_origin)
app.config['JSON_SORT_KEYS'] = False

@app.before_request
def before_request(): 
    try:
        if request.method == 'OPTIONS':
            return {"status": "error", "error_description": "OPTIONS OK"},200
        
        bypass_urls=('jwt_login','healthcheck', 'validate_demoauth') 
        if request.path.split('/')[1] in bypass_urls:
            return
        jwtlib = lockerjwt.LockerJwt(request)
        status_code, jwtres = jwtlib.jwt_login()
        
        if status_code == 200:
            g.digilockerid = jwtres['digilockerid']
            g.device_security_id = jwtres['device_security_id']
            g.token = jwtres['token']
            g.username = jwtres['username']
        else:
            return jwtres, status_code
    except Exception as e:
        return {"status": "error", "error_description": "Exception(JWT):" + str(e)},401

@app.route('/nominee/add', methods=['POST'])
def nominee_add():
    try: 
        username = g.username    
        mobile = CommonLib.filter_input(field=request.values.get('mobileno'))
        email = CommonLib.filter_input(field=request.values.get('email'))
        dob = CommonLib.filter_input(field=request.values.get('dob'))
        aadhaar = CommonLib.filter_input(field=request.values.get('uid'))
        if (dob != None and aadhaar != None):
            date = dob
            day = date[0:2]   
            month = date[2:4] 
            year = date[4:] 
            dob_format = datetime(int(year),int(month),int(day))
            name = CommonLib.filter_input(field=request.values.get('name'))
            gender = CommonLib.filter_input(field=request.values.get('gender'))
            relation = CommonLib.filter_input(field=request.values.get('relation'))
          
            form_validation = nominee_connector.form_validation(mobile,email,dob,aadhaar, name, gender, relation)
            if form_validation['status'] == 'error':
                return form_validation
            datanominee = {   
                "username" : username,
                "name" : name,
                "dob" : dob_format.strftime("%Y-%m-%d"),
                "gender" : gender,
                "relation" : relation,
                "uid" : aadhaar,
                "mobileno" : mobile, 
                'email' : email, 
                "consent" : 'Y' 
            }
            '''validate demo auth call'''
            demoresp = nominee_connector.validate_demoauth(datanominee)
           
            if(not(demoresp['status'])):
                return 200,{ 'status' :"error" ,'error_description':"Data entered does not match with the Aadhaar details. Please enter correct details as per Aadhaar"}
            if(demoresp['status']):
                datanominee['uid_token'] = demoresp['uid_token']
                datanominee['uid'] = hashlib.md5(((str(aadhaar)).strip()).encode()).hexdigest()
                datanominee['nominee_id'] = str(uuid.uuid5(uuid.uuid4(),str(random.randrange(0,2147483647))))
                datanominee['created_on'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                dob_val = datanominee['dob']
                datanominee['dob'] = dob_val
                
                '''mongodb function call''' 

                where = {"user_id":urllib.parse.quote(username)}
                res_mongo = MongoLib.find('users', where, limit = 1, projection = {'_id': 0})
                
                for i in res_mongo:  
                    user_mongodata = i 
                       
                if(user_mongodata['uid_token'] == datanominee['uid_token']):
                    return { "status" : "error","error_description":'You can not add yourself as nominee of your account'}               
              
                '''redis function call '''
                nominee_connector.set_redis_data(key=username + '_nominee', value=json.dumps(datanominee), ex=9999)
                
                '''rabbitmq function call'''
                datanominee = str(datanominee) 
                 
                RabbitMQAccounts.account_nominee(datanominee) 
               
                return { "status" : "success","error_description":'Nominee added successfully'}
            else:
                return {"status":"error","error_description" :"erorr in validate demo auth"}
        else:
            return {"status":"error","error_description" :'Could not add nominee'}
    except Exception as e:
        return {"status": "error", "error_description": str(e)}

@app.route('/nominee/update', methods=['POST'])
def nominee_update():  
    try:
        username = g.username 
        nominee_id = CommonLib.filter_input(field=request.values.get('nominee_id'))
        relation_val = CommonLib.filter_input(field=request.values.get('relation'))
        mobile_val = CommonLib.filter_input(field=request.values.get('mobileno'))
        email_val = CommonLib.filter_input(field=request.values.get('email'))

        relation = Validation.relation_validation(relation_val)
        mobile = Validation.mobile_validation(mobile_val)
        email = Validation.email_validation(email_val) 

        ''' can update relation,mobile and email only '''

        if (nominee_id != None):
            datanominee = {}  
            if(relation != None):
                datanominee['relation'] = relation_val
            if(mobile != None):
                datanominee['mobileno'] = mobile_val
            if(email != None):
                datanominee['email'] = email_val 

            datanominee['modified_on'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")        
            datanominee['nominee_id'] = nominee_id       
            
            ''' redis remove function ''' 
            nominee_connector.remove_redis_data(key = username + '_nominee') 

            ''' rabbitmq account_nominee function '''
            RabbitMQAccounts.account_nominee(datanominee) 

            return {"status":"success","error_description":"updated nominee details"}
        else:
            return {"status":"error","error_description":"Could not edit nominee"}
    except Exception as e:
        return {"status": "error", "error_description": "error in updating nominee" + str(e)}

@app.route('/nominee/delete', methods=['POST']) 
def nominee_delete():  
    try:  
        username = g.username 
        nominee_id = CommonLib.filter_input(field=request.values.get('nominee_id'))
        if (nominee_id != None): 
            datanominee = {}
            datanominee['is_deleted'] = 'Y'
            datanominee['deleted_on'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") 
            datanominee['nominee_id'] = nominee_id

            '''redis remove function'''
            nominee_connector.remove_redis_data(key = username + '_nominee') 
            
            '''rabbitmq account_nominee function''' 
            RabbitMQAccounts.account_nominee(datanominee) 

            return {"status":"success","error_description":"nominee deleted successfully"}
        else:
            return 200,{"status":"error","error_description":"Could not delete nominee"}
    except Exception as e:
        return {"status": "error", "error_description": "error in deleting nominee" + str(e)}

@app.route('/nominee/get', methods=['GET'])
def nominee_get(): 
    try:
        username = g.username  

        '''redis get function call'''
        nomineedata_redis = nominee_connector.get_redis_data(key = username + '_nominee')    
        nomineedata = nomineedata_redis["redis_data"]

        if  nomineedata is not None:
            return nomineedata

        if(nomineedata is None and username is not None): 
            
            '''mongodb function'''

            fieldswhere = {"username":urllib.parse.quote(username) , "_is_deleted" : { '$exists': 0 }}  
            profile_get = MongoLib.find('account_nominee', fieldswhere ,limit = 1,sort = ("created_on",-1),projection={"_id":0,"name":1,"dob":1,"gender":1,"relation":1,"uid":1,"mobileno":1,"email":1,"modified_on":1,"nominee_id":1,"_is_deleted":1,"created_on":1})
            
            for i in profile_get: 
                result = i 
                  
            if(result is not None): 
                nomineedata = result 
                encoded_data = encode_json(nomineedata)  
                
                ''' redis set function call ''' 
                nominee_connector.set_redis_data(key=username + '_nominee', value=json.dumps(encoded_data), ex=9999)
            else:          
                response = { "status":"error","error_description" : "No Nominee Found"}
                return response 

            nomineedata['status'] = 'success'  
            response = nomineedata 
            return response 
        else:
            response = {"status": "error" , "error_description" : 'No Nominee Found.'}
            return response
    except Exception as e:
        return {"status": "error", "error_description":str(e)}

@app.route('/family/register', methods=['POST']) 
def family_register():
    try:
        aadhaar = CommonLib.filter_input(field=request.values.get('aadhaar'))
        name = CommonLib.filter_input(field=request.values.get('name'))
        dob = CommonLib.filter_input(field=request.values.get('dob'))
        gender = CommonLib.filter_input(field=request.values.get('gender'))
        relation = CommonLib.filter_input(field=request.values.get('relation'))
        mobile = CommonLib.filter_input(field=request.values.get('mobile'))
        txn = str(uuid.uuid4())
        
        form_validation = family_connector.form_validation(aadhaar, name, dob, gender, relation, mobile)
        
        if form_validation['status'] == 'error':
            return form_validation
        
        # CHECK FOR txn whether exists in users digilockerid
        res = family_connector.check_unique_uuid(txn)
        if res:
            txn = res
        else:
            # we can generate txn again...ask to sir?
            # return {"status": "error", "error_description": 'User alreday exists with given txnId'}
            txn = str(uuid.uuid4())

        #encrypting aadhaar
        secret = bytes(os.getenv('JWT_SECRET'), 'utf-8')
        jwtlib = lockerjwt.LockerJwt(request)
        aadhaarHash = jwtlib.aes_encryption(aadhaar,secret)
        # aadhaar_dec = jwtlib.aes_decryption(aadhaarHash,secret)
        
        #preparing data
        data = {
            'aadhaar':aadhaarHash,
            'name':name,
            'dob':dob,
            'gender':gender,
            'relation':relation,
            'mobile':mobile,
            'txn':txn
        }
        digilockerid = g.digilockerid
        #storing data in redis and mongodb
        return family_connector.store_family_data(digilockerid, data)

    except Exception as e:
        print(str(e))
        return {"status": "error", "error_description": str(e)}

@app.route('/family/verify_pin', methods=['POST'])
def verify_pin():
    try:
        pin = CommonLib.filter_input(field=request.values.get('pin'))
        txn = CommonLib.filter_input(field=request.values.get('txn'))
        digilockerid = g.digilockerid
        
        #txn is valid or not check from redis  
        redis_result = family_connector.get_redis_data(digilockerid+'_'+txn+'_family_details')
        
        if redis_result is None:
            return {"status": "error", "error_description": 'Session Expired.'}
        
        redis_data = json.loads(redis_result)
        
        # pin verification function
        valid_pin = Validation.pin_validation(pin)
        if valid_pin[1]['status']=='error':
            return {'status': 'error', 'error_description': valid_pin[1]['error_description']}

        data = family_connector.verify_pin(digilockerid, pin)
        if data['status'] == 'success':
            
            registered_mobile = data['mobile']
            family_mobile = redis_data['mobile']
            if registered_mobile == family_mobile:
                #do demographic auth
                secret = bytes(os.getenv('JWT_SECRET'), 'utf-8')
                jwtlib = lockerjwt.LockerJwt(request)
                aadhaar_dec = jwtlib.aes_decryption(redis_data['aadhaar'],secret)
                redis_data['aadhaar']=aadhaar_dec
                demoauth_res = family_connector.demoAuth(redis_data, digilockerid)
                demoauth_res['next_flow'] = 'NA' 
                return demoauth_res
            else:
                #send otp on family mobile given
                send_otp_status= otp_connector.send_mobile_otp(family_mobile)
                if send_otp_status['status'] == 'error':
                    return send_otp_status
                send_otp_status['next_flow'] = 'verify_otp'
                return send_otp_status
        
        else:
            return {"status": "error", "error_description": 'error'}
    except Exception as e:
        print(str(e))
        return {"status": "error", "error_description": str(e)}

@app.route('/family/send_mobile_otp', methods=['POST'])
def send_mobile_otp():
    try:
        txn = CommonLib.filter_input(field=request.values.get('txn'))

        digilockerid = g.digilockerid
        redis_result = family_connector.get_redis_data(digilockerid+'_'+txn+'_family_details')
        if redis_result is None:
            return {"status": "error", "error_description": 'Session Expired.'}
        
        redis_data = json.loads(redis_result)
        print('4------------------', redis_data)
        res = otp_connector.send_mobile_otp(redis_data['mobile'])
        return res
        
    except Exception as e:
        print(str(e))
        return {"status": "error", "error_description": str(e)}

@app.route('/family/verify_mobile_otp', methods=['POST'])
def verify_mobile_otp():
    try:
        txn = CommonLib.filter_input(field=request.values.get('txn'))
        otp = CommonLib.filter_input(field=request.values.get('otp'))
        valid_otp = Validation.otp_validation(otp)

        
        if valid_otp[1]['status'] == 'error':
            return {"status": "error", "error_description": valid_otp[1]['error_description']}
        digilockerid = g.digilockerid
        redis_result = family_connector.get_redis_data(digilockerid+'_'+txn+'_family_details')
        if redis_result is None:
            return {"status": "error", "error_description": 'Session Expired.'}
        
        redis_data = json.loads(redis_result)

        res = otp_connector.verify_mobile_otp(redis_data['mobile'], otp)
        if res['status']=='error':
            return res
        
        secret = bytes(os.getenv('JWT_SECRET'), 'utf-8')
        jwtlib = lockerjwt.LockerJwt(request)
        aadhaar_dec = jwtlib.aes_decryption(redis_data['aadhaar'],secret)
        redis_data['aadhaar'] = aadhaar_dec

        #create account once otp verifies successfully    
        return family_connector.demoAuth(redis_data, digilockerid)
        
    except Exception as e:
        print(str(e))
        return {"status": "error", "error_description": str(e)}

# getting the family list from parent digilocker id
@app.route('/family/family_list', methods=['GET'])
def family_list():
    try:
        # getting the digilocker id from jw token
        digilockerid = CommonLib.filter_input(field=g.digilockerid)

        # filter where all the created_by field will be shown which is matching with the digilocker_id
        where = {"created_by": digilockerid}
        data = MongoLib.find('users_profile', where, projection={'_id': 0,'user_from': 0,'account_type':0,'converted_by': 0,'created_on': 0,'_v': 0})
        res = []
        for x in data:
            if x['digilockerid'] != x['created_by']:
                res.append(x)
                print(res)

        return {"status": "success", "data":res}
    except Exception as e:
        return {"status": "error", "error_description": str(e)}

@app.route('/family/issued_doc', methods=['POST'])
def issued_doc():
    try:
        url = 'https://idss.digitallocker.gov.in/index.php/apps/issued_docs/api/1.0/issueddocs'
        
        # getting the digilocker id from jw token
        digilockerid = CommonLib.filter_input(field=g.digilockerid)
        # filter where all the created_by field will be shown which is matching with the digilocker_id
        where = {"created_by": digilockerid}
        data = MongoLib.find('users_profile', where, projection={'_id': 0,'user_from': 0,'created_by': 0,'account_type':0,'converted_by': 0,'created_on': 0,'_v': 0})
        res = []
        jwtlib = lockerjwt.LockerJwt(request)
        for x in data:
            x['jwt'] = jwtlib.jwt_generate(x['digilockerid'])
            headers = {'jtoken': x['jwt'],'device-security-id':g.device_security_id}
            response = requests.request("POST", url, headers=headers)
            issued_docs = json.loads(response.text)
            # print(issued_docs)
            res[x['digilockerid']] = issued_docs
            
        return {"status": "success", "data": res}

    except Exception as e:
        return {"status": "error", "error_description": str(e)}

@app.route('/family/relationship', methods=['GET'])
def relationship():
    relations = {'status': 'success',
                 'data': [{'name': 'Partner'}, {'name': 'Son'}, {'name': 'Daughter'}, {'name': 'Mother'},
                          {'name': 'Father'}, {'name': 'Sister'}, {
                              'name': 'Brother'}, {'name': 'Mother-in-law'},
                          {'name': 'Sister-in-law'}, {'name': 'Uncle'}, {
                              'name': 'Aunt'}, {'name': 'Cousin'},
                          {'name': 'Grand Father'}, {'name': 'Grand Mother'}, {'name': 'Others'}]}
    return relations

@app.route('/validate_demoauth', methods=['POST'])
def validate_demoauth():
    try:
        hmac = request.values.get('hmac')
        res, status_code = CommonLib.validate_input(request)
        if status_code == 200:
            uid, fullname, gender, dob, clientid, ts = res
        else:
            return res, status_code
        data = {   
                "uid" : uid,
                "name" : fullname,
                "gender" : gender,
                "dob" : dob,
                "consent" : 'Y'
            }
        
        res, status = CommonLib.check_secret_validate_demoauth(data, clientid, ts, hmac)
        if status == 400:
            return res
        
        return nominee_connector.validate_demoauth(data)

    except Exception as e:
        return {"status": "error", "error_description": "validate_demo" +str(e)}

@app.route('/healthcheck', methods=['GET', 'POST'])
def healthcheck():
    return {"status": "success"}

debug_mode = True
if os.getenv('DEBUG_MODE') == 'False': 
    debug_mode = True
WSGIRequestHandler.protocol_version = "HTTP/1.1"
app.run(host=os.getenv('HOST'), port=os.getenv('PORT'), debug=debug_mode)
