import base64
import hashlib
import inspect
import logging
import os
import random
import string
import sys
import textwrap
import re
import uuid
import warnings
from datetime   import datetime
from secrets    import token_bytes
from time import sleep
from urllib.parse import urlencode, quote_plus, unquote_plus

from dotenv     import load_dotenv

def append_random_string(target, length=6, prefix='-'):
    return f'{target}{random_string(length, prefix)}'

def bytes_md5(bytes : bytes):
    return hashlib.md5(bytes).hexdigest()

def bytes_sha256(bytes : bytes):
    return hashlib.sha256(bytes).hexdigest()

def base64_to_bytes(bytes_base64):
    if type(bytes_base64) is str:
        bytes_base64 = bytes_base64.encode()
    return base64.decodebytes(bytes_base64)

def base64_to_str(target):
    return bytes_to_str(base64_to_bytes(target))

def bytes_to_base64(bytes):
    return base64.b64encode(bytes).decode()

def bytes_to_str(target, encoding='ascii'):
    return target.decode(encoding=encoding)

def chunks(items:list, split: int):
    if items and split and split > 0:
        for i in range(0, len(items), split):
            yield items[i:i + split]

def class_name(target):
    if target:
        return type(target).__name__

def class_functions(target):
    functions = {}
    for function_name, function_ref in inspect.getmembers(type(target), predicate=inspect.isfunction):
        functions[function_name] = function_ref
    return functions

def class_functions_names(target):
    return list_set(class_functions(target))

def convert_to_number(value):
    if value:
        try:
            if value[0] in ['£','$','€']:
                return float(re.sub(r'[^\d.]', '', value))
            else:
                return float(value)
        except:
          return 0
    else:
        return 0

def date_now(use_utc=True, return_str=True):
    value = date_time_now(use_utc=use_utc, return_str=False)
    if return_str:
        return date_to_str(date=value)
    return value

def date_time_now(use_utc=True, return_str=True, milliseconds_numbers=0):
    if use_utc:
        value = datetime.utcnow()
    else:
        value = datetime.now()
    if return_str:
        return date_time_to_str(value, milliseconds_numbers=milliseconds_numbers)
    return value

def date_time_to_str(date_time, date_time_format='%Y-%m-%d %H:%M:%S.%f', milliseconds_numbers=3):
    date_time_str = date_time.strftime(date_time_format)
    return time_str_milliseconds(datetime_str=date_time_str, datetime_format=date_time_format, milliseconds_numbers=milliseconds_numbers)

def date_to_str(date, date_format='%Y-%m-%d'):
    return date.strftime(date_format)

def time_str_milliseconds(datetime_str, datetime_format, milliseconds_numbers=0):
    if '.%f' in datetime_format and -1 < milliseconds_numbers < 6:
        chars_to_remove = milliseconds_numbers-6
        if milliseconds_numbers == 0:
            chars_to_remove -= 1
        return datetime_str[:chars_to_remove]
    return datetime_str

def env_value(var_name):
    return env_vars().get(var_name, None)

def env_vars():
    """
    reload data from .env file and return dictionary with current environment variables
    """
    load_dotenv()
    vars = os.environ
    data = {}
    for key in vars:
        data[key] = vars[key]
    return data

def env_vars_list():
    return list_set(env_vars())

def flist(target):
    from osbot_utils.fluent.Fluent_List import Fluent_List
    return Fluent_List(target)

def get_field(target, field, default=None):
    if target is not None:
        try:
            value = getattr(target, field)
            if value is not None:
                return value
        except:
            pass
    return default

def get_value(target, key, default=None):
    if target is not None:
        value = target.get(key)
        if value is not None:
            return value
    return default

# todo: check if this should still be here
def get_random_color(max=5):
    if max > 5: max = 5                                                             # add support for more than 5 colors
    colors = ['skyblue', 'darkseagreen', 'palevioletred', 'coral', 'darkgray']
    return colors[random_number(0, max-1)]

def get_missing_fields(target,field):
    missing_fields = []
    for field in field:
        if get_field(target, field) is None:
            missing_fields.append(field)
    return missing_fields

def is_number(value):
    try:
        if type(value) is int or type(value) is float :
            int(value)
            return True
    except:
        pass
    return False

def ignore_warning__unclosed_ssl():
    warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")


def last_letter(text):
    if text and (type(text) is str) and len(text) > 0:
        return text[-1]

def len_list(target):
    return len(list(target))

def list_add(array : list, value):
    if value is not None:
        array.append(value)
    return value

def list_contains_list(array : list, values):
    if array is not None:
        if type(values) is list:
            for item in values:
                if item in array is False:
                    return False
            return True
    return False

def list_find(array:list, item):
    if item in array:
        return array.index(item)
    return -1

def list_get_field(values, field):
    return [item.get(field) for item in values]

def list_index_by(values, index_by):
    from osbot_utils.fluent.Fluent_Dict import Fluent_Dict
    results = {}
    if values and index_by:
        for item in values:
            results[item.get(index_by)] = item
    return Fluent_Dict(results)

def list_group_by(values, group_by):
    results = {}
    for item in values:
        value = item.get(group_by)
        if results.get(value) is None: results[value] = []
        results[value].append(item)
    return results

def list_get(array, position=None, default=None):
    if type(array) is list:
        if type(position) is int and position >=0 :
            if  len(array) > position:
                return array[position]
    return default

def list_pop(array:list, position=None, default=None):
    if array:
        if len(array) >0:
            if type(position) is int:
                if len(array) > position:
                    return array.pop(position)
            else:
                return array.pop()
    return default

def list_pop_and_trim(array, position=None):
    value = array_pop(array,position)
    if type(value) is str:
        return trim(value)
    return value

def list_set(target):
    if target:
        return sorted(list(set(target)))
    return []

def list_zip(*args):
    return list(zip(*args))


def list_set_dict(target):
    return sorted(list(set(obj_dict(target))))

def list_filter(target_list, filter_function):
    return list(filter(filter_function, target_list))

def list_sorted(target_list, key, descending=False):
    return list(sorted(target_list, key= lambda x:x.get(key,None) ,reverse=descending))

def list_filter_starts_with(target_list, prefix):
    return list_filter(target_list, lambda x: x.startswith(prefix))

def list_filter_contains(target_list, value):
    return list_filter(target_list, lambda x: x.find(value) > -1)

def log_critical(message): logger().critical(message) # level 50
def log_debug   (message): logger().debug   (message) # level 10
def log_error   (message): logger().error   (message) # level 40
def log_info    (message): logger().info    (message) # level 20
def log_warning (message): logger().warning (message) # level 30

def log_to_console(level="INFO"):
    logger_set_level(level)
    logger_add_handler__console()
    print()                             # add extra print so that in pytest the first line is not hidden

def log_to_file(level="INFO"):
    logger_set_level(level)
    return logger_add_handler__file()

def logger():
    return logging.getLogger()

def logger_add_handler(handler):
    logger().addHandler(handler)

def logger_add_handler__console():
    logger_add_handler(logging.StreamHandler())

def logger_add_handler__file(log_file=None):
    from osbot_utils.utils.Files import temp_file
    log_file = log_file or temp_file(extension=".log")
    logger_add_handler(logging.FileHandler(filename=log_file))
    return log_file
    #logging.basicConfig(level=logging.DEBUG, filename='myapp.log', format='%(asctime)s %(levelname)s:%(message)s')

def logger_set_level(level):
    logger().setLevel(level)

def logger_set_level_critical(): logger_set_level('CRITICAL') # level 50
def logger_set_level_debug   (): logger_set_level('DEBUG'   ) # level 10
def logger_set_level_error   (): logger_set_level('ERROR'   ) # level 40
def logger_set_level_info    (): logger_set_level('INFO'    ) # level 20
def logger_set_level_warning (): logger_set_level('WARNING' ) # level 30

def lower(target : str):
    if target:
        return target.lower()
    return ""

def obj_data(target=None):
    data = {}
    for key,value in obj_items(target):
        data[key] = value
    return data

def obj_dict(target=None):
    if target and hasattr(target,'__dict__'):
        return target.__dict__
    return {}

def obj_items(target=None):
    return sorted(list(obj_dict(target).items()))

def obj_keys(target=None):
    return sorted(list(obj_dict(target).keys()))

def obj_get_value(target=None, key=None, default=None):
    return get_field(target=target, field=key, default=default)

def obj_values(target=None):
    return list(obj_dict(target).values())


def size(target=None):
    if target and hasattr(target, '__len__'):
        return len(target)
    return 0

def str_index(target:str, source:str):
    try:
        return target.index(source)
    except:
        return -1

def sys_path_python(python_folder='lib/python'):
    return list_contains(sys.path, python_folder)

def str_md5(text : str):
    if text:
        return bytes_md5(text.encode())
    return None

def none_or_empty(target,field):
    if target and field:
        value = target.get(field)
        return (value is None) or value == ''
    return True

def print_date_now(use_utc=True):
    print(date_time_now(use_utc=use_utc))

def print_object_members(target, max_width=120, show_internals=False):
    print()
    print(f"Members for object: {target}"[:max_width])
    print()
    print(f"{'field':<20} | value")
    print(f"{'-' * max_width}")
    for name, val in inspect.getmembers(target):
        if not show_internals and name.startswith("__"):
            continue
        print(f"{name:<20} | {val}"[:max_width])

def print_time_now(use_utc=True):
    print(time_now(use_utc=use_utc))

def str_sha256(text: str):
    if text:
        return bytes_sha256(text.encode())
        #return hashlib.sha256('{0}'.format(text).encode()).hexdigest()
    return None

def time_delta_to_str(time_delta):
    microseconds  = time_delta.microseconds
    milliseconds  = int(microseconds / 1000)
    total_seconds = int(time_delta.total_seconds())
    return f'{total_seconds}s {milliseconds}ms'

def time_now(use_utc=True, milliseconds_numbers=1):
    if use_utc:
        datetime_now = datetime.utcnow()
    else:
        datetime_now = datetime.now()
    return time_to_str(datetime_value=datetime_now,milliseconds_numbers=milliseconds_numbers)

def time_to_str(datetime_value, time_format='%H:%M:%S.%f', milliseconds_numbers=3):
    time_str = datetime_value.strftime(time_format)
    return time_str_milliseconds(datetime_str=time_str, datetime_format=time_format, milliseconds_numbers=milliseconds_numbers)

def timestamp_utc_now():
    return int(datetime.utcnow().timestamp() * 1000)
    return int(datetime.utcnow().strftime('%s')) * 1000

def timestamp_to_datetime(timestamp):
    return datetime.fromtimestamp(timestamp/1000)

def to_string(target):
    if target:
        return str(target)
    return ''

def random_bytes(length=24):
    return token_bytes(length)

def random_filename(extension='.tmp', length=10):
    from osbot_utils.utils.Files import file_extension_fix
    extension = file_extension_fix(extension)
    return '{0}{1}'.format(''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) ,  extension)

def random_port(min=20000,max=65000):
    return random_number(min, max)

def random_number(min=1,max=65000):
    return random.randint(min, max)

def random_password(length=24, prefix=''):
    password = prefix + ''.join(random.choices(string.ascii_lowercase  +
                                               string.ascii_uppercase +
                                               string.punctuation     +
                                               string.digits          ,
                                               k=length))
    # replace these chars with _  (to make prevent errors in command prompts and urls)
    items = ['"', '\'', '`','\\','/','}','?','#',';',':']
    for item in items:
        password = password.replace(item, '_')
    return password

def random_string(length=8,prefix=''):
    return prefix + ''.join(random.choices(string.ascii_uppercase, k=length)).lower()

def random_string_and_numbers(length=6,prefix=''):
    return prefix + ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))

def random_text(prefix=None,length=12):
    if prefix is None: prefix = 'text_'
    if last_letter(prefix) != '_':
        prefix += '_'
    return random_string_and_numbers(length=length, prefix=prefix)

def random_uuid():
    return str(uuid.uuid4())

def remove(target_string, string_to_remove):                        # todo: refactor to str_*
    return replace(target_string, string_to_remove, '')

def remove_multiple_spaces(target):                                 # todo: refactor to str_*
    return re.sub(' +', ' ', target)

def replace(target_string, string_to_find, string_to_replace):      # todo: refactor to str_*
    return target_string.replace(string_to_find, string_to_replace)

def remove_html_tags(html):
    if html:
        TAG_RE = re.compile(r'<[^>]+>')
        return TAG_RE.sub('', html).replace('&nbsp;', ' ')

def split_lines(text):
    return text.replace('\r\n','\n').split('\n')

def split_spaces(target):
    return remove_multiple_spaces(target).split(' ')

def sorted_set(target : object):
    if target:
        return sorted(set(target))
    return []

def str_to_base64(target):
    return bytes_to_base64(str_to_bytes(target))

def str_to_bytes(target):
    return target.encode()

def str_to_date(str_date, format='%Y-%m-%d %H:%M:%S.%f'):
    return datetime.strptime(str_date,format)

def to_int(value, default=0):
    try:
        return int(value)
    except:
        return default

def trim(target):
    if type(target) is str:
        return target.strip()
    return ""

def under_debugger():
    return 'pydevd' in sys.modules

def unique(target):
    return list_set(target)

def url_encode(data):
    if type(data) is str:
        return quote_plus(data)

def url_decode(data):
    if type(data) is str:
        return unquote_plus(data)

def upper(target : str):
    if target:
        return target.upper()
    return ""

def wait(seconds):
    sleep(seconds)

def word_wrap(text,length = 40):
    return '\n'.join(textwrap.wrap(text, length))

def word_wrap_escaped(text,length = 40):
    if text:
        return '\\n'.join(textwrap.wrap(text, length))


array_find          = list_find
array_get           = list_get
array_pop           = list_pop
array_pop_and_trim  = list_pop_and_trim
array_add           = list_add
bytes_to_string     = bytes_to_str
convert_to_float    = convert_to_number
datetime_now        = date_time_now
list_contains       = list_filter_contains
new_guid            = random_uuid
obj_list_set        = obj_keys
str_lines           = split_lines
str_remove          = remove
random_id           = random_string
wait_for            = wait


import base64
import hashlib
import inspect
import logging
import os
import random
import string
import sys
import textwrap
import re
import uuid
import warnings
from datetime   import datetime
from secrets    import token_bytes
from time import sleep
from urllib.parse import urlencode, quote_plus, unquote_plus

from dotenv     import load_dotenv

def append_random_string(target, length=6, prefix='-'):
    return f'{target}{random_string(length, prefix)}'

def bytes_md5(bytes : bytes):
    return hashlib.md5(bytes).hexdigest()

def bytes_sha256(bytes : bytes):
    return hashlib.sha256(bytes).hexdigest()

def base64_to_bytes(bytes_base64):
    if type(bytes_base64) is str:
        bytes_base64 = bytes_base64.encode()
    return base64.decodebytes(bytes_base64)

def base64_to_str(target):
    return bytes_to_str(base64_to_bytes(target))

def bytes_to_base64(bytes):
    return base64.b64encode(bytes).decode()

def bytes_to_str(target, encoding='ascii'):
    return target.decode(encoding=encoding)

def chunks(items:list, split: int):
    if items and split and split > 0:
        for i in range(0, len(items), split):
            yield items[i:i + split]

def class_name(target):
    if target:
        return type(target).__name__

def class_functions(target):
    functions = {}
    for function_name, function_ref in inspect.getmembers(type(target), predicate=inspect.isfunction):
        functions[function_name] = function_ref
    return functions

def class_functions_names(target):
    return list_set(class_functions(target))

def convert_to_number(value):
    if value:
        try:
            if value[0] in ['£','$','€']:
                return float(re.sub(r'[^\d.]', '', value))
            else:
                return float(value)
        except:
          return 0
    else:
        return 0

def date_now(use_utc=True, return_str=True):
    value = date_time_now(use_utc=use_utc, return_str=False)
    if return_str:
        return date_to_str(date=value)
    return value

def date_time_now(use_utc=True, return_str=True, milliseconds_numbers=0):
    if use_utc:
        value = datetime.utcnow()
    else:
        value = datetime.now()
    if return_str:
        return date_time_to_str(value, milliseconds_numbers=milliseconds_numbers)
    return value

def date_time_to_str(date_time, date_time_format='%Y-%m-%d %H:%M:%S.%f', milliseconds_numbers=3):
    date_time_str = date_time.strftime(date_time_format)
    return time_str_milliseconds(datetime_str=date_time_str, datetime_format=date_time_format, milliseconds_numbers=milliseconds_numbers)

def date_to_str(date, date_format='%Y-%m-%d'):
    return date.strftime(date_format)

def time_str_milliseconds(datetime_str, datetime_format, milliseconds_numbers=0):
    if '.%f' in datetime_format and -1 < milliseconds_numbers < 6:
        chars_to_remove = milliseconds_numbers-6
        if milliseconds_numbers == 0:
            chars_to_remove -= 1
        return datetime_str[:chars_to_remove]
    return datetime_str

def env_value(var_name):
    return env_vars().get(var_name, None)

def env_vars():
    """
    reload data from .env file and return dictionary with current environment variables
    """
    load_dotenv()
    vars = os.environ
    data = {}
    for key in vars:
        data[key] = vars[key]
    return data

def env_vars_list():
    return list_set(env_vars())

def flist(target):
    from osbot_utils.fluent.Fluent_List import Fluent_List
    return Fluent_List(target)

def get_field(target, field, default=None):
    if target is not None:
        try:
            value = getattr(target, field)
            if value is not None:
                return value
        except:
            pass
    return default

def get_value(target, key, default=None):
    if target is not None:
        value = target.get(key)
        if value is not None:
            return value
    return default

# todo: check if this should still be here
def get_random_color(max=5):
    if max > 5: max = 5                                                             # add support for more than 5 colors
    colors = ['skyblue', 'darkseagreen', 'palevioletred', 'coral', 'darkgray']
    return colors[random_number(0, max-1)]

def get_missing_fields(target,field):
    missing_fields = []
    for field in field:
        if get_field(target, field) is None:
            missing_fields.append(field)
    return missing_fields

def is_number(value):
    try:
        if type(value) is int or type(value) is float :
            int(value)
            return True
    except:
        pass
    return False

def ignore_warning__unclosed_ssl():
    warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")


def last_letter(text):
    if text and (type(text) is str) and len(text) > 0:
        return text[-1]

def len_list(target):
    return len(list(target))

def list_add(array : list, value):
    if value is not None:
        array.append(value)
    return value

def list_contains_list(array : list, values):
    if array is not None:
        if type(values) is list:
            for item in values:
                if item in array is False:
                    return False
            return True
    return False

def list_find(array:list, item):
    if item in array:
        return array.index(item)
    return -1

def list_get_field(values, field):
    return [item.get(field) for item in values]

def list_index_by(values, index_by):
    from osbot_utils.fluent.Fluent_Dict import Fluent_Dict
    results = {}
    if values and index_by:
        for item in values:
            results[item.get(index_by)] = item
    return Fluent_Dict(results)

def list_group_by(values, group_by):
    results = {}
    for item in values:
        value = item.get(group_by)
        if results.get(value) is None: results[value] = []
        results[value].append(item)
    return results

def list_get(array, position=None, default=None):
    if type(array) is list:
        if type(position) is int and position >=0 :
            if  len(array) > position:
                return array[position]
    return default

def list_pop(array:list, position=None, default=None):
    if array:
        if len(array) >0:
            if type(position) is int:
                if len(array) > position:
                    return array.pop(position)
            else:
                return array.pop()
    return default

def list_pop_and_trim(array, position=None):
    value = array_pop(array,position)
    if type(value) is str:
        return trim(value)
    return value

def list_set(target):
    if target:
        return sorted(list(set(target)))
    return []

def list_zip(*args):
    return list(zip(*args))


def list_set_dict(target):
    return sorted(list(set(obj_dict(target))))

def list_filter(target_list, filter_function):
    return list(filter(filter_function, target_list))

def list_sorted(target_list, key, descending=False):
    return list(sorted(target_list, key= lambda x:x.get(key,None) ,reverse=descending))

def list_filter_starts_with(target_list, prefix):
    return list_filter(target_list, lambda x: x.startswith(prefix))

def list_filter_contains(target_list, value):
    return list_filter(target_list, lambda x: x.find(value) > -1)

def log_critical(message): logger().critical(message) # level 50
def log_debug   (message): logger().debug   (message) # level 10
def log_error   (message): logger().error   (message) # level 40
def log_info    (message): logger().info    (message) # level 20
def log_warning (message): logger().warning (message) # level 30

def log_to_console(level="INFO"):
    logger_set_level(level)
    logger_add_handler__console()
    print()                             # add extra print so that in pytest the first line is not hidden

def log_to_file(level="INFO"):
    logger_set_level(level)
    return logger_add_handler__file()

def logger():
    return logging.getLogger()

def logger_add_handler(handler):
    logger().addHandler(handler)

def logger_add_handler__console():
    logger_add_handler(logging.StreamHandler())

def logger_add_handler__file(log_file=None):
    from osbot_utils.utils.Files import temp_file
    log_file = log_file or temp_file(extension=".log")
    logger_add_handler(logging.FileHandler(filename=log_file))
    return log_file
    #logging.basicConfig(level=logging.DEBUG, filename='myapp.log', format='%(asctime)s %(levelname)s:%(message)s')

def logger_set_level(level):
    logger().setLevel(level)

def logger_set_level_critical(): logger_set_level('CRITICAL') # level 50
def logger_set_level_debug   (): logger_set_level('DEBUG'   ) # level 10
def logger_set_level_error   (): logger_set_level('ERROR'   ) # level 40
def logger_set_level_info    (): logger_set_level('INFO'    ) # level 20
def logger_set_level_warning (): logger_set_level('WARNING' ) # level 30

def lower(target : str):
    if target:
        return target.lower()
    return ""

def obj_data(target=None):
    data = {}
    for key,value in obj_items(target):
        data[key] = value
    return data

def obj_dict(target=None):
    if target and hasattr(target,'__dict__'):
        return target.__dict__
    return {}

def obj_items(target=None):
    return sorted(list(obj_dict(target).items()))

def obj_keys(target=None):
    return sorted(list(obj_dict(target).keys()))

def obj_get_value(target=None, key=None, default=None):
    return get_field(target=target, field=key, default=default)

def obj_values(target=None):
    return list(obj_dict(target).values())


def size(target=None):
    if target and hasattr(target, '__len__'):
        return len(target)
    return 0

def str_index(target:str, source:str):
    try:
        return target.index(source)
    except:
        return -1

def sys_path_python(python_folder='lib/python'):
    return list_contains(sys.path, python_folder)

def str_md5(text : str):
    if text:
        return bytes_md5(text.encode())
    return None

def none_or_empty(target,field):
    if target and field:
        value = target.get(field)
        return (value is None) or value == ''
    return True

def print_date_now(use_utc=True):
    print(date_time_now(use_utc=use_utc))

def print_object_members(target, max_width=120, show_internals=False):
    print()
    print(f"Members for object: {target}"[:max_width])
    print()
    print(f"{'field':<20} | value")
    print(f"{'-' * max_width}")
    for name, val in inspect.getmembers(target):
        if not show_internals and name.startswith("__"):
            continue
        print(f"{name:<20} | {val}"[:max_width])

def print_time_now(use_utc=True):
    print(time_now(use_utc=use_utc))

def str_sha256(text: str):
    if text:
        return bytes_sha256(text.encode())
        #return hashlib.sha256('{0}'.format(text).encode()).hexdigest()
    return None

def time_delta_to_str(time_delta):
    microseconds  = time_delta.microseconds
    milliseconds  = int(microseconds / 1000)
    total_seconds = int(time_delta.total_seconds())
    return f'{total_seconds}s {milliseconds}ms'

def time_now(use_utc=True, milliseconds_numbers=1):
    if use_utc:
        datetime_now = datetime.utcnow()
    else:
        datetime_now = datetime.now()
    return time_to_str(datetime_value=datetime_now,milliseconds_numbers=milliseconds_numbers)

def time_to_str(datetime_value, time_format='%H:%M:%S.%f', milliseconds_numbers=3):
    time_str = datetime_value.strftime(time_format)
    return time_str_milliseconds(datetime_str=time_str, datetime_format=time_format, milliseconds_numbers=milliseconds_numbers)

def timestamp_utc_now():
    return int(datetime.utcnow().timestamp() * 1000)
    return int(datetime.utcnow().strftime('%s')) * 1000

def timestamp_to_datetime(timestamp):
    return datetime.fromtimestamp(timestamp/1000)

def to_string(target):
    if target:
        return str(target)
    return ''

def random_bytes(length=24):
    return token_bytes(length)

def random_filename(extension='.tmp', length=10):
    from osbot_utils.utils.Files import file_extension_fix
    extension = file_extension_fix(extension)
    return '{0}{1}'.format(''.join(random.choices(string.ascii_lowercase + string.digits, k=length)) ,  extension)

def random_port(min=20000,max=65000):
    return random_number(min, max)

def random_number(min=1,max=65000):
    return random.randint(min, max)

def random_password(length=24, prefix=''):
    password = prefix + ''.join(random.choices(string.ascii_lowercase  +
                                               string.ascii_uppercase +
                                               string.punctuation     +
                                               string.digits          ,
                                               k=length))
    # replace these chars with _  (to make prevent errors in command prompts and urls)
    items = ['"', '\'', '`','\\','/','}','?','#',';',':']
    for item in items:
        password = password.replace(item, '_')
    return password

def random_string(length=8,prefix=''):
    return prefix + ''.join(random.choices(string.ascii_uppercase, k=length)).lower()

def random_string_and_numbers(length=6,prefix=''):
    return prefix + ''.join(random.choices(string.ascii_uppercase + string.digits, k=length))

def random_text(prefix=None,length=12):
    if prefix is None: prefix = 'text_'
    if last_letter(prefix) != '_':
        prefix += '_'
    return random_string_and_numbers(length=length, prefix=prefix)

def random_uuid():
    return str(uuid.uuid4())

def remove(target_string, string_to_remove):                        # todo: refactor to str_*
    return replace(target_string, string_to_remove, '')

def remove_multiple_spaces(target):                                 # todo: refactor to str_*
    return re.sub(' +', ' ', target)

def replace(target_string, string_to_find, string_to_replace):      # todo: refactor to str_*
    return target_string.replace(string_to_find, string_to_replace)

def remove_html_tags(html):
    if html:
        TAG_RE = re.compile(r'<[^>]+>')
        return TAG_RE.sub('', html).replace('&nbsp;', ' ')

def split_lines(text):
    return text.replace('\r\n','\n').split('\n')

def split_spaces(target):
    return remove_multiple_spaces(target).split(' ')

def sorted_set(target : object):
    if target:
        return sorted(set(target))
    return []

def str_to_base64(target):
    return bytes_to_base64(str_to_bytes(target))

def str_to_bytes(target):
    return target.encode()

def str_to_date(str_date, format='%Y-%m-%d %H:%M:%S.%f'):
    return datetime.strptime(str_date,format)

def to_int(value, default=0):
    try:
        return int(value)
    except:
        return default

def trim(target):
    if type(target) is str:
        return target.strip()
    return ""

def under_debugger():
    return 'pydevd' in sys.modules

def unique(target):
    return list_set(target)

def url_encode(data):
    if type(data) is str:
        return quote_plus(data)

def url_decode(data):
    if type(data) is str:
        return unquote_plus(data)

def upper(target : str):
    if target:
        return target.upper()
    return ""

def wait(seconds):
    sleep(seconds)

def word_wrap(text,length = 40):
    return '\n'.join(textwrap.wrap(text, length))

def word_wrap_escaped(text,length = 40):
    if text:
        return '\\n'.join(textwrap.wrap(text, length))


array_find          = list_find
array_get           = list_get
array_pop           = list_pop
array_pop_and_trim  = list_pop_and_trim
array_add           = list_add
bytes_to_string     = bytes_to_str
convert_to_float    = convert_to_number
datetime_now        = date_time_now
list_contains       = list_filter_contains
new_guid            = random_uuid
obj_list_set        = obj_keys
str_lines           = split_lines
str_remove          = remove
random_id           = random_string
wait_for            = wait


reload data from .env file and return dictionary with current environment variables # todo: check if this should still be here # add support for more than 5 colors # level 50 # level 10 # level 40 # level 20 # level 30 # add extra print so that in pytest the first line is not hidden #logging.basicConfig(level=logging.DEBUG, filename='myapp.log', format='%(asctime)s %(levelname)s:%(message)s') # level 50 # level 10 # level 40 # level 20 # level 30 #return hashlib.sha256('{0}'.format(text).encode()).hexdigest() # replace these chars with _  (to make prevent errors in command prompts and urls) # todo: refactor to str_* # todo: refactor to str_* # todo: refactor to str_*