# By: Riasat Ullah
# This file contains functions for generating all types of keys to be used around.

from base64 import urlsafe_b64encode, urlsafe_b64decode
from validations import string_validator
import configuration as configs
import datetime
import random
import string
import uuid


def generate_verification_code(code_length):
    '''
    Generates a verification code given a particular length requirement.
    :param code_length: length of the code
    :return: (str) verification code
    '''
    code = ''
    for i in range(0, code_length):
        code += str(random.randint(0, 9))
    return code


def generate_account_number():
    '''
    Gets a new account number for the organization.
    :return: (str) account number
    '''
    reducer = '20092381293121'
    return str(int(datetime.datetime.now().strftime('%Y%m%d%H%M%S')) - int(reducer)) +\
        str(random.randint(0, 9)) + str(random.randint(0, 9))


def generate_reference_key():
    '''
    Generate a random reference key.
    :return: (UUID) reference key
    '''
    return uuid.uuid4()


def conceal_reference_key(key: uuid.UUID):
    '''
    Conceals a reference key to a url safe string.
    :param key: (UUID) reference key in original form
    :return: (str) url safe reference key
    '''
    return urlsafe_b64encode(key.bytes).decode('utf-8').rstrip('=')


def unmask_reference_key(concealed_key: str):
    '''
    Unmasks a concealed reference key.
    :param concealed_key: (str) concealed reference key
    :return: (UUID) reference key in original form
    '''
    return uuid.UUID(urlsafe_b64decode((concealed_key + '==').encode('utf-8')).hex())


def generate_api_key(region):
    '''
    Creates a new API key.
    :param region: the hosting region the API key is for
    '''
    return ''.join(random.choice(string.ascii_letters + string.digits) for i in range(configs.api_key_length[region]))


def generate_alphanumeric_key(length, capitalized=False):
    '''
    Generate a random reference key.
    :param length: (int) length of the key
    :param capitalized: (boolean) if the key is needed in capitalized form
    :return: (str) key
    '''
    key = uuid.uuid4().hex[:length]
    if capitalized:
        return key.upper()
    return ''.join(random.choice((str.upper, str.lower))(c) for c in key)


def conceal_object_keys(di, upd_k, last_key=None):
    '''
    Find all reference keys given a list of key names and conceal them.
    :param di: (dict or list) object to traverse through
    :param upd_k: (list) of keys to look for and conceal
    :param last_key: (str) the last key that was traversed (will be needed for sub lists)
    '''
    if isinstance(di, list):
        for i in range(0, len(di)):
            sub_v = di[i]

            if isinstance(sub_v, tuple):
                sub_v = list(sub_v)
                di[i] = sub_v

            if isinstance(sub_v, uuid.UUID):
                if last_key is not None and last_key in upd_k:
                    di[i] = conceal_reference_key(sub_v)
            elif isinstance(sub_v, str):
                if last_key is not None and last_key in upd_k and string_validator.is_valid_uuid(sub_v):
                    di[i] = conceal_reference_key(uuid.UUID(sub_v))
            elif isinstance(sub_v, dict) or isinstance(sub_v, list):
                conceal_object_keys(sub_v, upd_k, last_key)

    elif isinstance(di, dict):
        for k, v in di.items():
            if isinstance(v, tuple):
                v = list(v)
                di[k] = v

            if isinstance(v, uuid.UUID):
                if k in upd_k:
                    di[k] = conceal_reference_key(v)
            elif isinstance(v, str):
                if k in upd_k:
                    di[k] = conceal_reference_key(uuid.UUID(v))
            elif isinstance(v, dict) or isinstance(v, list):
                conceal_object_keys(v, upd_k, last_key=k)


def generate_random_string_key(length=None):
    '''
    Generate a random string that can be used as a key
    :param length: the size of the string
    :return: (str) random string
    '''
    if length is None:
        length = random.randint(50, 100)
    return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(length))


def attach_service_type_to_ref_id(concealed_ref, is_technical):
    '''
    Attach 'tech_' or 'bus_' to service reference ID depending on whether it is a technical or business service.
    :param concealed_ref: (str) concealed reference ID
    :param is_technical: (boolean) True if the service is technical; False otherwise
    :return: (str) -> reference ID with service type attached in front
    '''
    return 'tech' + '_' + concealed_ref if is_technical else 'bus' + '_' + concealed_ref


def detach_service_type_from_ref_id(concealed_ref):
    '''
    Detach 'tech_' or 'bus_' from service reference ID.
    :param concealed_ref: (str) service type attached concealed reference ID
    :return: (tuple) -> (reference ID without service type attached in front, is technical)
    '''
    sep_index = concealed_ref.find('_')
    srv_type = concealed_ref[0: sep_index]
    new_ref = concealed_ref[sep_index + 1:]
    if srv_type == 'tech':
        return new_ref, True
    else:
        return new_ref, False


def encode_general_identifier(item_list):
    '''
    Joins a list of strings by the '+' operator and then encodes it.
    :param item_list: (list) of string objects
    :return: (str) url safe encoded identifier
    '''
    conjoined_id = '+'.join(item_list)
    identifier = urlsafe_b64encode(bytes(conjoined_id, "utf-8")).decode("utf-8").rstrip("=")
    return identifier


def decode_general_identifier(identifier):
    '''
    Decodes an identifier and gets the list of strings that initially formed it.
    :param identifier: (str) url safe encoded identifier
    :return: (list) of string objects
    '''
    item_list = urlsafe_b64decode((identifier + "==").encode("utf-8")).decode().split('+')
    return item_list
