import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..')))
from komrade import *
from komrade.backend.crypt import *
from abc import ABC, abstractmethod
 
class KomradeKey(ABC):
    @abstractmethod
    def encrypt(self,msg,**kwargs): pass
    @abstractmethod
    def decrypt(self,msg,**kwargs): pass

    @abstractmethod
    def data(self): pass


class KomradeSymmetricKey(KomradeKey):
    @property
    def cell(self):
        if not hasattr(self,'_cell'):
            if hasattr(self,'passphrase') and self.passphrase:
                self._cell = SCellSeal(passphrase=self.passphrase)
            elif hasattr(self,'key') and self.key:
                self._cell = SCellSeal(key=self.key)
        return self._cell
    def encrypt(self,msg,**kwargs):
        if issubclass(type(msg), KomradeKey): msg=msg.data
        return self.cell.encrypt(msg,**kwargs)
    def decrypt(self,msg,**kwargs):
        return self.cell.decrypt(msg,**kwargs)
    
    


        
class KomradeSymmetricKeyWithPassphrase(KomradeSymmetricKey):
    def __init__(self,passphrase=None, why=WHY_MSG):
        self.passphrase=passphrase
        if not self.passphrase:
            self.passphrase=getpass.getpass(why)
        #return self.passphrase
    @property
    def data(self): return KEY_TYPE_SYMMETRIC_WITH_PASSPHRASE.encode('utf-8')

class KomradeSymmetricKeyWithoutPassphrase(KomradeSymmetricKey):
    def __init__(self,key=None):
        self.key = GenerateSymmetricKey() if not key else key
    @property
    def data(self): return self.key



class KomradeAsymmetricKey(KomradeKey):
    def __init__(self,pubkey,privkey):
        self.pubkey=pubkey
        self.privkey=privkey
    def encrypt(self,msg,pubkey=None,privkey=None):
        if issubclass(type(msg), KomradeKey): msg=msg.data
        pubkey=pubkey if pubkey else self.pubkey
        privkey=privkey if privkey else self.privkey
        return SMessage(privkey,pubkey).wrap(msg)
    def decrypt(self,msg,pubkey=None,privkey=None):
        pubkey=pubkey if pubkey else self.pubkey
        privkey=privkey if privkey else self.privkey
        return SMessage(privkey,pubkey).unwrap(msg)
    @property
    def data(self): return self.key

class KomradeAsymmetricPublicKey(KomradeAsymmetricKey):
    @property
    def key(self): return self.pubkey
class KomradeAsymmetricPrivateKey(KomradeAsymmetricKey):
    @property
    def key(self): return self.privkey


class Keymaker(Logger):
    def __init__(self,name=None,passphrase=None,keychain={}, path_crypt_keys=None, path_crypt_data=None, allow_builtin=True):
        self.name=name
        self._keychain=keychain
        self.passphrase=passphrase
        self.path_crypt_keys=path_crypt_keys
        self.path_crypt_data=path_crypt_data
        self.allow_builtin=allow_builtin


    ### BASE STORAGE
    @property
    def crypt_keys(self):
        if not hasattr(self,'_crypt_keys'):
            self._crypt_keys = Crypt(fn=self.path_crypt_keys)
        return self._crypt_keys

    @property
    def crypt_keys_mem(self):
        if not hasattr(self,'_crypt_keys_mem'):
            self._crypt_keys_mem = CryptMemory()
        return self._crypt_keys_mem
        
    @property
    def crypt_data(self):
        if not hasattr(self,'_crypt_data'):
            self._crypt_data = Crypt(fn=self.path_crypt_data)
        return self._crypt_data


    ### STARTING WITH MOST ABSTRACT

    def findkey(self, keyname, keychain={}, uri=None):
        # self.log(f'looking for key {keyname}, in keychain {keychain.keys()} or under crypt uri {uri}')
        # look in keychain, then in crypt, for this key
        given_key = keychain.get(keyname)
        if given_key:
            # self.log(f'{keyname} found in keychain: {given_key}')
            return given_key

        found_key = self.crypt_keys.get(uri,prefix=f'/{keyname}/')
        if found_key:
            # self.log(f'{keyname} found in crypt: {given_key}')
            return found_key

        # self.log(f'{keyname} not found!!')
            

    def getkey(self, keyname, keychain={}, uri=None):
        # cached?
        if hasattr(self,'_'+keyname) and getattr(self,'_'+keyname):
            return getattr(self,'_'+keyname)
        if keyname in self._keychain: return self._keychain[keyname]

        # self.log(f'keyname={keyname}, keychain={keychain.keys()}, uri={uri}')

        # 1) I already have this key stored in either the keychain or the crypt; return straight away
        key = self.findkey(keyname, keychain, uri)
        if key:
            # self.log(f'>> I have {key} already, returning')
            return key

        ## 2) I can assemble the key
        # self.log(f'assembling key: {keyname}_encr + {keyname}_decr')
        key_encr = self.findkey(keyname+'_encr', keychain,uri)
        key_decr = self.findkey(keyname+'_decr', keychain, uri)
        key = self.assemble_key(key_encr, key_decr)
        return key

    def get_cell(self, str_or_key_or_cell):
        # self.log('getting decr cell for',str_or_key_or_cell)

        if type(str_or_key_or_cell)==SCellSeal:
            return str_or_key_or_cell
        elif type(str_or_key_or_cell)==str:
            return SCellSeal(passphrase=str_or_key_or_cell)
        elif type(str_or_key_or_cell)==bytes:
            return SCellSeal(key=str_or_key_or_cell)

    def assemble_key(self, key_encr, key_decr):
        # self.log(f'assembling key: {key_decr} decrypting {key_encr}')

        # need the encrypted half
        if not key_encr:
            # self.log('!! encrypted half not given')
            return
        if not key_decr:
            if self.passphrase:
                key_decr = self.passphrase
            else:
                # self.log('!! decryptor half not given')
                return

        # need some way to regenerate the decryptor
        decr_cell = self.get_cell(key_decr)

        # need the decryptor half
        if not decr_cell:
            # self.log('!! decryptor cell not regenerable')
            return

        # decrypt!
        try:
            self.log(f'>> decrypting {key_encr} with cell {decr_cell}')
            key = decr_cell.decrypt(key_encr)
            # self.log('assembled_key built:',key)
            return key
        except ThemisError as e:
            self.log('!! decryption failed:',e)
            return

    # Concrete keys
    ## (1) Final keys
    def pubkey(self, force=False, **kwargs):
        if force or not hasattr(self,'_pubkey') or not self._pubkey:
            self._pubkey = self.getkey(keyname='pubkey',uri=self.name,**kwargs)
        return self._pubkey

    def privkey(self, force=False, **kwargs):
        if force or not hasattr(self,'_privkey') or not self._privkey:
            self._privkey=self.getkey(keyname='privkey',uri=self.pubkey(**kwargs),**kwargs)
        return self._privkey

    def adminkey(self, force=False, **kwargs):
        if force or not hasattr(self,'_adminkey') or not self._adminkey:
            self._adminkey=self.getkey(keyname='adminkey',uri=self.privkey(**kwargs),**kwargs)
        return self._adminkey

    ## (1-X) Encrypted halves
    def pubkey_encr(self, **kwargs):
        return self.getkey(uri=self.name,keyname='pubkey_encr',**kwargs)
    def privkey_encr(self, **kwargs):
        return self.getkey(uri=self.pubkey(**kwargs),keyname='privkey_encr',**kwargs)
    def adminkey_encr(self, **kwargs):
        return self.getkey(uri=self.privkey(**kwargs),keyname='adminkey_encr',**kwargs)

    ## (1-Y) Decrpytor halves
    def pubkey_decr(self, **kwargs):
        return self.getkey(uri=self.name,keyname='pubkey_decr',**kwargs)
    def privkey_decr(self, **kwargs):
        return self.getkey(uri=self.pubkey(**kwargs),keyname='privkey_decr',**kwargs)
    def adminkey_decr(self, **kwargs):
        return self.getkey(uri=self.privkey(**kwargs),keyname='adminkey_decr',**kwargs)

    ## Second halving!
    ## (1-X-X)
    def pubkey_encr_encr(self, **kwargs):
        return self.getkey(uri=self.name,keyname='pubkey_encr_encr',**kwargs)
    def privkey_encr_encr(self, **kwargs):
        return self.getkey(uri=self.pubkey_encr(**kwargs),keyname='privkey_encr_encr',**kwargs)
    def adminkey_encr_encr(self, **kwargs):
        return self.getkey(uri=self.privkey_encr(**kwargs),keyname='adminkey_encr_encr',**kwargs)

    ## (1-X-Y)
    def pubkey_encr_decr(self, **kwargs):
        return self.getkey(uri=self.name,keyname='pubkey_encr_decr',**kwargs)
    def privkey_encr_decr(self, **kwargs):
        return self.getkey(uri=self.pubkey_encr(**kwargs),keyname='privkey_encr_decr',**kwargs)
    def adminkey_encr_decr(self, **kwargs):
        return self.getkey(uri=self.privkey_encr(**kwargs),keyname='adminkey_encr_decr',**kwargs)

    ## (1-Y-X)
    def pubkey_decr_encr(self, **kwargs):
        return self.getkey(uri=self.name,keyname='pubkey_decr_encr',**kwargs)
    def privkey_decr_encr(self, **kwargs):
        return self.getkey(uri=self.pubkey_decr(**kwargs),keyname='privkey_decr_encr',**kwargs)
    def adminkey_decr_encr(self, **kwargs):
        return self.getkey(uri=self.privkey_decr(**kwargs),keyname='adminkey_decr_encr',**kwargs)

    ## (1-Y-Y)
    def pubkey_decr_decr(self, **kwargs):
        return self.getkey(uri=self.name,keyname='pubkey_decr_decr',**kwargs)
    def privkey_decr_decr(self, **kwargs):
        return self.getkey(uri=self.pubkey_decr(**kwargs),keyname='privkey_decr_decr',**kwargs)
    def adminkey_decr_decr(self, **kwargs):
        return self.getkey(uri=self.privkey_decr(**kwargs),keyname='adminkey_decr_decr',**kwargs)


    # convenience functions
    
    # Concrete keys
    @property
    def pubkey_(self): return self.keychain().get('pubkey')
    @property
    def privkey_(self, **kwargs): return self.keychain().get('privkey')
    @property
    def adminkey_(self, **kwargs): return self.keychain().get('adminkey')
    
    ## (1-X) Encrypted halves
    @property
    def pubkey_encr_(self, **kwargs):return self.keychain().get('pubkey_encr')
    @property
    def privkey_encr_(self, **kwargs): return self.keychain().get('privkey_encr')
    @property
    def adminkey_encr_(self, **kwargs): return self.keychain().get('adminkey_encr')

    ## (1-Y) Decrpytor halves
    @property
    def pubkey_decr_(self, **kwargs): return self.keychain().get('pubkey_decr')
    @property
    def privkey_decr_(self, **kwargs): return self.keychain().get('privkey_decr')
    @property
    def adminkey_decr_(self, **kwargs): return self.keychain().get('adminkey_decr')

    ## Second halving!
    ## (1-X-X)
    @property
    def pubkey_encr_encr_(self, **kwargs): return self.keychain().get('pubkey_encr_encr')
    @property
    def privkey_encr_encr_(self, **kwargs): return self.keychain().get('privkey_encr_encr')
    @property
    def adminkey_encr_encr_(self, **kwargs): return self.keychain().get('adminkey_encr_encr')

    ## (1-X-Y)
    @property
    def pubkey_encr_decr_(self, **kwargs): return self.keychain().get('pubkey_encr_decr')
    @property
    def privkey_encr_decr_(self, **kwargs): return self.keychain().get('privkey_encr_decr')
    @property
    def adminkey_encr_decr_(self, **kwargs): return self.keychain().get('adminkey_encr_decr')

    ## (1-Y-X)
    @property
    def pubkey_decr_encr_(self, **kwargs): return self.keychain().get('pubkey_decr_encr')
    @property
    def privkey_decr_encr_(self, **kwargs): return self.keychain().get('privkey_decr_encr')
    @property
    def adminkey_decr_encr_(self, **kwargs): return self.keychain().get('adminkey_decr_encr')

    ## (1-Y-Y)
    @property
    def pubkey_decr_decr_(self, **kwargs): return self.keychain().get('pubkey_decr_decr')
    @property
    def privkey_decr_decr_(self, **kwargs): return self.keychain().get('privkey_decr_decr')
    @property
    def adminkey_decr_decr_(self, **kwargs): return self.keychain().get('adminkey_decr_decr')


    # Get key de-cryptors
    def genkey_pass_keycell(self,pass_phrase,q_name='Read permissions?'):
        if pass_phrase is None:
            pass_key = GenerateSymmetricKey()
            pass_cell = SCellSeal(key=pass_key)
        else:
            if pass_phrase is True: pass_phrase=getpass.getpass(f'Enter pass phrase [{q_name}]: ')
            pass_key = None
            pass_cell = SCellSeal(passphrase=pass_phrase)

        self.log(f'pass_key [{q_name}] <--',pass_key)
        self.log(f'pass_cell [{q_name}] <--',pass_cell)
        return (pass_key, pass_cell)



    def exists(self):
        return self.crypt_keys.exists(self.name,prefix='/pubkey_encr/') or self.crypt_keys.exists(self.name,prefix='/pubkey_decr/') or self.crypt_keys.exists(self.name,prefix='/pubkey/')

    ### CREATING KEYS
    
    def get_new_keys(self):
        raise KomradeException('Every keymaker must make their own get_new_keys() !')

    def gen_keys_from_types(self,key_types,passphrase=None):
        asymmetric_pubkey=None
        asymmetric_privkey=None
        keychain = {}
        for key_name,key_type_desc in key_types.items():
            if key_type_desc in {KEY_TYPE_ASYMMETRIC_PUBKEY,KEY_TYPE_ASYMMETRIC_PRIVKEY}:
                if not asymmetric_privkey or not asymmetric_pubkey:
                    keypair = GenerateKeyPair(KEY_PAIR_TYPE.EC)
                    asymmetric_privkey = keypair.export_private_key()
                    asymmetric_pubkey = keypair.export_public_key()

            if key_type_desc==KEY_TYPE_ASYMMETRIC_PRIVKEY:
                keychain[key_name] = KomradeAsymmetricPrivateKey(asymmetric_pubkey,asymmetric_privkey)
            elif key_type_desc==KEY_TYPE_ASYMMETRIC_PUBKEY:
                keychain[key_name] = KomradeAsymmetricPublicKey(asymmetric_pubkey,asymmetric_privkey)
            elif key_type_desc==KEY_TYPE_SYMMETRIC_WITHOUT_PASSPHRASE:
                keychain[key_name]=KomradeSymmetricKeyWithoutPassphrase()
            elif key_type_desc==KEY_TYPE_SYMMETRIC_WITH_PASSPHRASE:
                if not passphrase and not self.passphrase: self.passphrase=getpass.getpass(WHY_MSG)
                passphrase=passphrase if passphrase else self.passphrase
                keychain[key_name]=KomradeSymmetricKeyWithPassphrase(passphrase=passphrase)
        return keychain

    def gen_encr_keys(self,keychain,keys_to_gen,passphrase=None):
        # generate encrypted keys too
        for key_name in keys_to_gen:
            if key_name.endswith('_encr') and key_name not in keychain:
                # encrypt it with the associated decr
                self.log(f'let\'s encrypt {key_name}!')
                name_of_what_to_encrypt = key_name[:-len('_encr')]
                the_key_to_encrypt_it_with = name_of_what_to_encrypt + '_decr'
                if the_key_to_encrypt_it_with in keychain and name_of_what_to_encrypt in keychain:
                    _key_decr = keychain[the_key_to_encrypt_it_with]
                    _key = keychain[name_of_what_to_encrypt]
                    self.log(f'about to encrypt key {name_of_what_to_encrypt}, using {the_key_to_encrypt_it_with}, which is a type {KEYMAKER_DEFAULT_KEY_TYPES[the_key_to_encrypt_it_with]} and has value {keychain[the_key_to_encrypt_it_with]}')
                    _key_encr = _key_decr.encrypt(_key)
                    # self.log(f'{_key}\n-- encrypting ----->\n{_key_encr}')
                    keychain[key_name]=_key_encr
        return keychain



    def forge_new_keys(self,
                        name=None,
                        passphrase=None,
                        keys_to_save = KEYMAKER_DEFAULT_KEYS_TO_SAVE,
                        keys_to_return = KEYMAKER_DEFAULT_KEYS_TO_RETURN,
                        keys_to_gen = KEYMAKER_DEFAULT_KEYS_TO_GEN,
                        key_types = KEYMAKER_DEFAULT_KEY_TYPES):
        self.log('forging new keys...',name,self.name)
        self.log('keys_to_save:',keys_to_save)
        self.log('keys_to_return',keys_to_return)
        


        if not name: name=self.name

        keys_to_gen = set(keys_to_gen) | set(keys_to_save) | set(keys_to_return)
        keys_to_gen = sorted(list(keys_to_gen),key=lambda x: x.count('_'))
        self.log('keys_to_gen =',keys_to_gen)
        key_types = dict([(k,key_types[k]) for k in keys_to_gen])
        self.log('key_types =',key_types)

        # get decryptor keys!
        keychain = self.gen_keys_from_types(key_types,passphrase=passphrase)
        self.log('keychain 1 =',keychain)
        
        # gen encrypted keys!
        keychain = self.gen_encr_keys(keychain,keys_to_gen,passphrase=passphrase)
        self.log('keychain 2 =',keychain)

        # save keys!
        keys_saved = self.save_keychain(name,keychain,keys_to_save)
        self.log('keys_saved =',keys_saved)

        # return keys!
        keys_returned = self.return_keychain(keychain,keys_to_return)
        return keys_returned
        
                
    def return_keychain(self,keychain,keys_to_return):
        keychain_toreturn = {}
        for key in keys_to_return:
            if key in keychain:
                keychain_toreturn[key]=keychain[key]
        return keychain_toreturn


    def save_keychain(self,name,keychain,keys_to_save):
        keys_saved = []

        # filter for transfer
        for k,v in keychain.items():
            if issubclass(type(v),KomradeKey):
                v=v.data
            keychain[k]=v

        # for k,v in keychain_tosave.items():        
        if 'pubkey' in keys_to_save or 'privkey' in keys_to_save or 'adminkey' in keys_to_save:
            # unless we're the operator or the telephone
            if not self.name in {OPERATOR_NAME,TELEPHONE_NAME}:
                raise KomradeException('there is no private property in a socialist network! all keys must be split between komrades')
            else:
                if 'pubkey' in keys_to_save and 'pubkey' in keychain:
                    keys_saved+=['pubkey']
                    self.crypt_keys.set(name,keychain['pubkey'],prefix='/pubkey/')
                    
                if 'privkey' in keys_to_save and 'privkey' in keychain:
                    keys_saved+=['privkey']
                    self.crypt_keys.set(keychain['pubkey'],keychain['privkey'],prefix='/privkey/')
                
                if 'adminkey' in keys_to_save and 'adminkey' in keychain:
                    keys_saved+=['adminkey']
                    self.crypt_keys.set(keychain['privkey'],keychain['adminkey'],prefix='/adminkey/')
                

        ### SAVE ENCRYPTED KEYS?        
        if 'pubkey_encr' in keys_to_save and 'pubkey_encr' in keychain:
            keys_saved+=['pubkey_encr']
            self.crypt_keys.set(name,keychain['pubkey_encr'],prefix='/pubkey_encr/')
        
        if 'privkey_encr' in keys_to_save and 'privkey_encr' in keychain:
            keys_saved+=['privkey_encr']
            self.crypt_keys.set(keychain['pubkey'],keychain['privkey_encr'],prefix='/privkey_encr/')
        
        if 'adminkey_encr' in keys_to_save and 'adminkey_encr' in keychain:
            keys_saved+=['adminkey_encr']
            self.crypt_keys.set(keychain['privkey'],keychain['adminkey_encr'],prefix='/adminkey_encr/')
        # stop

        # save decrypted keys?
        if 'pubkey_decr' in keys_to_save and 'pubkey_decr' in keychain:
            keys_saved+=['pubkey_decr']
            self.crypt_keys.set(name,keychain['pubkey_decr'],prefix='/pubkey_decr/')
        
        if 'privkey_decr' in keys_to_save and 'privkey_decr' in keychain:
            keys_saved+=['privkey_decr']
            self.crypt_keys.set(keychain['pubkey'],keychain['privkey_decr'],prefix='/privkey_decr/')
        
        if 'adminkey_decr' in keys_to_save and 'adminkey_decr' in keychain:
            keys_saved+=['adminkey_decr']
            self.crypt_keys.set(keychain['privkey'],keychain['adminkey_decr'],prefix='/adminkey_decr/')
        
        if 'pubkey_encr_encr' in keys_to_save and 'pubkey_encr_encr' in keychain:
            keys_saved+=['pubkey_encr_encr']
            self.crypt_keys.set(name,keychain['pubkey_decr_encr'],prefix='/pubkey_decr_encr/')
        
        if 'privkey_encr_encr' in keys_to_save and 'privkey_encr_encr' in keychain:
            keys_saved+=['privkey_encr_encr']    
            self.crypt_keys.set(keychain['pubkey_decr'],keychain['privkey_decr_encr'],prefix='/privkey_decr_encr/')
        
        if 'adminkey_encr_encr' in keys_to_save and 'adminkey_encr_encr' in keychain:
            keys_saved+=['adminkey_encr_encr']
            self.crypt_keys.set(keychain['privkey_decr'],keychain['adminkey_decr_encr'],prefix='/adminkey_decr_encr/')

        if 'pubkey_decr_encr' in keys_to_save and 'pubkey_decr_encr' in keychain:
            keys_saved+=['pubkey_decr_encr']
            self.crypt_keys.set(name,keychain['pubkey_decr_encr'],prefix='/pubkey_decr_encr/')
        
        if 'privkey_decr_encr' in keys_to_save and 'privkey_decr_encr' in keychain:
            keys_saved+=['privkey_decr_encr']
            self.crypt_keys.set(keychain['pubkey_decr'],keychain['privkey_decr_encr'],prefix='/privkey_decr_encr/')
        
        if 'adminkey_decr_encr' in keys_to_save and 'adminkey_decr_encr' in keychain:
            keys_saved+=['adminkey_decr_encr']
            self.crypt_keys.set(keychain['privkey_decr'],keychain['adminkey_decr_encr'],prefix='/adminkey_decr_encr/')
        
        if 'pubkey_decr_decr' in keys_to_save and 'pubkey_decr_decr' in keychain:
            keys_saved+=['pubkey_decr_decr']
            self.crypt_keys.set(name,keychain['pubkey_decr_decr'],prefix='/pubkey_decr_decr/')
        
        if 'privkey_decr_decr' in keys_to_save and 'privkey_decr_decr' in keychain:
            keys_saved+=['privkey_encr_encr']
            self.crypt_keys.set(keychain['pubkey_decr'],keychain['privkey_decr_decr'],prefix='/privkey_decr_decr/')
        
        if 'adminkey_decr_decr' in keys_to_save and 'adminkey_decr_decr' in keychain:
            keys_saved+=['adminkey_decr_decr']
            self.crypt_keys.set(keychain['privkey_decr'],keychain['adminkey_decr_decr'],prefix='/adminkey_decr_decr/')
        
        if 'pubkey_decr_decr' in keys_to_save and 'pubkey_decr_decr' in keychain:
            keys_saved+=['pubkey_decr_decr']
            self.crypt_keys.set(name,keychain['pubkey_decr_decr'],prefix='/pubkey_decr_decr/')
        
        if 'privkey_decr_decr' in keys_to_save and 'privkey_decr_decr' in keychain:
            keys_saved+=['privkey_encr_encr']
            self.crypt_keys.set(keychain['pubkey_decr'],keychain['privkey_decr_decr'],prefix='/privkey_decr_decr/')
        
        if 'adminkey_decr_decr' in keys_to_save and 'adminkey_decr_decr' in keychain:
            keys_saved+=['adminkey_decr_decr']
            self.crypt_keys.set(keychain['privkey_decr'],keychain['adminkey_decr_decr'],prefix='/adminkey_decr_decr/')

        # return in dict form
        keys_saved_d = {}
        for key_saved in keys_saved:
            if key_saved in keychain:
                keys_saved_d[key_saved] = keychain[key_saved]
        return keys_saved_d


    def valid_keychain(self,keychain_b64_d):
        valid_kc = {}

        for k,v in keychain_b64_d.items():
            self.log('incoming <--',k,v)
            if type(v)==bytes: 
                data = v
            elif type(v)==str:
                if not isBase64(v):
                    self.log('not valid input!')
                    continue
                
                # first try to get from string to bytes
                try:
                    data = v.encode()
                    self.log('data',data)
                except UnicodeEncodeError:
                    self.log('not valid unicode?')
                    continue
            
            # then try to get from b64 bytes to raw bytes
            if isBase64(data):
                valid_kc[k]=b64decode(data)
            else:
                valid_kc[k]=data
        
        return valid_kc

    def keychain(self,passphrase=None,force=False,allow_builtin=True,extra_keys={},keys_to_gen=KEYMAKER_DEFAULT_KEYS_TO_GEN,**kwargs):
        # assemble as many keys as we can!
        # if not force and hasattr(self,'_keychain') and self._keychain: return self._keychain
        if passphrase: self.passphrase=passphrase
        _keychain = {**extra_keys, **self._keychain}
        self.log('_keychain at start of keychain() =',_keychain)
        for keyname in keys_to_gen:
            if keyname in _keychain and _keychain[keyname]: continue
            # self.log('??',keyname,'...')
            if hasattr(self,keyname):
                method=getattr(self,keyname)
                res=method(keychain=_keychain, **kwargs)
                # self.log('res <--',res)
                if res:
                    _keychain[keyname]=res
        self._keychain = _keychain
        return _keychain
        


if __name__ == '__main__':
    keymaker = Keymaker('marx69')
    keychain = keymaker.forge_new_keys()

    print(keychain)