import secrets as s, abc as a, random, time
from cryptography.hazmat.primitives.asymmetric import rsa
from .bip39_wordlist import wordlist
rnd = s.SystemRandom()


def gencode_int(length: int = 10):
    result = []
    for _ in range(length): result.append(rnd.randrange(0, 9))
    result1 = ''
    for i in result: result1 += str(i)
    return result1

def gencode_int_sections(sections: int = 2, one_section_length: int = 4, separator: str = '-'):
    sections_tmp = []
    for i in range(sections): sections_tmp += [gencode_int(one_section_length)]
    result2 = ''
    for i in sections_tmp:
        for li in i: result2 += str(li)
        result2 += separator
    return result2[:-1]

def gencode_str(length: int = 10):
    alphabet = list('1234567890' +
                    'QwErTyUiOpAsDfGhJkLzXcVbNmqWeRtYuIoPaSdFgHjKlZxCvBnM')
    result = []
    for _ in range(length): result.append(rnd.choice(alphabet))
    return ''.join(result)

def gencode_str_withsymbols(length: int = 10):
    alphabet = list('1234567890' +
                    'QwErTyUiOpAsDfGhJkLzXcVbNmqWeRtYuIoPaSdFgHjKlZxCvBnM' + 
                    '!@#$%&*_+/?')
    result = []
    for _ in range(length): result.append(rnd.choice(alphabet))
    return ''.join(result)

def gencode_str_symbols(length: int = 10):
    alphabet = list('!@#$%&*_+/?')
    result = []
    for _ in range(length): result.append(rnd.choice(alphabet))
    return ''.join(result)

def gencode_str_sections(sections: int = 4, one_section_length: int = 5, separator: str = '-'):
    sections_tmp = []
    for i in range(sections): sections_tmp += [gencode_str(one_section_length)]
    return separator.join(sections_tmp)

def genpass(length: int = 20):
    if length < 8: raise ValueError(f"Can't generate password with length < 8. Current length: [{length}].")
    res = []
    for i in range(length): 
        if i % 3 == 0: res += gencode_int(1)
        if 4 % (i + 1) == 0: res += gencode_str_symbols(1)
        else: res += gencode_str_withsymbols(1)
    return ''.join(res)

def gen_mnemonic_phrase(word_count=12):
    return ' '.join(s.choice(wordlist) for _ in range(word_count))


def generate_rsa_keys(bits=2048):
    private_key = rsa.generate_private_key(public_exponent=65537, key_size=bits)
    return private_key, private_key.public_key()


def gen_key(k_entropy: int = 10, minlenght_gen: int = 2, min_output_length: int = 40, salt: int = 0, max_len: int = 80, rand_steps: int = 0, timestamp: bool = True):
    key = ''
    attemps = 0
    if len(str(salt)) > 2:
        raise ValueError('Salt must be from 0 to 99.')
    def gen(key, i):
        if 2 % i == 0 or 6 % i == 0: key += str(random_system.randint(1, min_output_length))
        elif 3 % i == 0 or 7 % i == 0: key += gencode_int(minlenght_gen)
        elif 4 % i == 0 or 8 % i == 0: key += gencode_str(minlenght_gen)
        elif 5 % i == 0 or 9 % i == 0: key += gencode_str_symbols(minlenght_gen)
        else: key += gencode_str_withsymbols(minlenght_gen)
        return key
    while True:
        random_system = s.SystemRandom()
        while min_output_length >= len(key):
            for i in range(k_entropy):
                i = i + random_system.randint(1, 2)
                key = gen(key, i)
                for _ in range(rand_steps):
                    lcl = random_system.randint(1, 10)
                    key = gen(key, lcl)
            rand = random_system.randint(1, 2)
            key += str(salt) + str(len(key)) if rand == 1 else str(len(key)) + str(salt)
        if attemps >= 3: 
            key = ''.join(list(key)[0:round(min_output_length*1.5)])
            break
        attemps += 1
        if len(key) < min_output_length*2: break
        else: key = ''
    key = key + str(int(time.time() * 1000000))[-8:] if timestamp else key
    return ''.join(key[:max_len])



class ClassApi(a.ABC):
    def __init__(self):
        super().__init__()
    
    @staticmethod
    def gen_type(type: str = 'int', length: int = 10):
        'type: int/str/symbol'
        if type == 'int': return gencode_int(length)
        if type == 'symbol': return gencode_str_symbols(length)
        else: return gencode_str(length)
    
    @staticmethod
    def gen_type_sections(type: str = 'int', 
                          sections: int = 4, 
                          one_section_length: int = 5, 
                          separator: str = '-'):
        'type: int/str'
        if type == 'int': return gencode_int_sections(sections, one_section_length, separator)
        else: return gencode_str_sections(sections, one_section_length, separator)
    
    @staticmethod
    def gen_password(length: int = 8): return genpass(length)

    @staticmethod
    def gen_key(k_ent: int = 10, 
                min_gen_len: int = 30, 
                min_out_len: int = 40, 
                max_out_len: int = 80,
                salt: int = 0,
                rand_steps: int = 0,
                time_stamp: bool = True):
        return gen_key(k_ent, min_gen_len, min_out_len, salt, max_out_len, rand_steps, time_stamp)
    
    @staticmethod
    def gen_rsa_keys(bits: int = 2048):
        return generate_rsa_keys(bits)

    @staticmethod
    def gen_mnemonic(length: int = 10): return gen_mnemonic_phrase(length)

    @staticmethod
    def generate_rsa_keys(bits: int = 2048): return generate_rsa_keys(bits)

if __name__ == '__main__':
    key = gen_key(299, 1, 30)
    print(key, len(key))
    input()
    history = []
    start = time.time()
    for _ in range(10_000): 
        key = gen_key(random.randrange(2, 40), 2, 45, 22, 50)
        if key in history: raise ValueError(f'Except: {key}')
        print(key, len(key), len(history))
        history.append(key)
    end = time.time()
    print(f'By {end-start}')
    time.sleep(1)
    '''
    tests:
    gen_key(9, 1, 6) - 0 collisions, 105 seconds
    gen_key(9, 1, 10) - 0 collisions, 118 seconds
    gen_key(9, 1, 4) - collision at 29435
    gen_key(12, 1, 4) - collision at 34848
    gen_key(12, 1, 4) - collision at 31739
    gen_key(40, 1, 5) - 0 collisions, 380 seconds
    gen_key(2, 1, 100) - 0 collisions, 107 seconds
    '''
    history = []
    for _ in range(100_000): 
        key = gen_key(10, 3, 45, 22, 50, 2)
        if key in history: raise ValueError(f'Except: {key}')
        print(key, len(key), len(history))
        history.append(key)
    end = time.time()
    print(f'By {end-start}')