# internal imports
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 komrade.backend.keymaker import *
# from komrade.backend.mazes import *
# from komrade.backend.switchboard import *
from komrade.backend import *
        

def locate_an_operator(name):
    global OPERATOR,TELEPHONE

    from komrade.backend.the_operator import TheOperator
    from komrade.backend.the_telephone import TheTelephone
    from komrade.backend.callers import Caller

    if name == OPERATOR_NAME:
        return OPERATOR if OPERATOR else TheOperator()
    if name == TELEPHONE_NAME:
        return TELEPHONE if TELEPHONE else TheTelephone()
    return Caller(name)


class Operator(Keymaker):
    ROUTES = ['forge_new_keys','does_username_exist','hello_world']
    
    def __init__(self, name, passphrase=DEBUG_DEFAULT_PASSPHRASE, keychain = {}, path_crypt_keys=PATH_CRYPT_CA_KEYS, path_crypt_data=PATH_CRYPT_CA_DATA):
        super().__init__(name=name,passphrase=passphrase, keychain=keychain,
                         path_crypt_keys=path_crypt_keys, path_crypt_data=path_crypt_data)
        # self.boot(create=False)

        # connect phonelines?
        from komrade.backend.phonelines import connect_phonelines
        try:
            self.operator_keychain,self.telephone_keychain,self.world_keychain,self.omega_key = connect_phonelines()
        except KeyError:
            pass

    # def boot(self,create=False):
    #      # Do I have my keys?
    #     have_keys = self.exists()
        
    #     # If not, forge them -- only once!
    #     if not have_keys and create:
    #         self.get_new_keys()
        
    
    @property
    def phone(self):
        from komrade.backend.the_telephone import TheTelephone
        if type(self)==TheTelephone: return self

        if hasattr(self,'_phone'): return self._phone

        global TELEPHONE,TELEPHONE_KEYCHAIN
        if TELEPHONE: return TELEPHONE

        self._phone=TELEPHONE=TheTelephone()

        return TELEPHONE

    @property
    def op(self):
        from komrade.backend.the_operator import TheOperator
        if type(self)==TheOperator: return self

        if hasattr(self,'_op'): return self._op

        global OPERATOR,OPERATOR_KEYCHAIN
        if OPERATOR: return OPERATOR
        
        self._op=OPERATOR=TheOperator()
        
        return OPERATOR


    def compose_msg_to(self,msg,another):
        if not self.privkey or not self.pubkey:
            raise KomradeException('why do I have no pub/privkey pair!?',self,self.name,self.pubkey,self.privkey,self.keychain())
        if not another.name or not another.pubkey:
            raise KomradeException('why do I not know whom I\'m writing to?')

        # otherwise create msg
        msg_d = {
            '_from_pub':self.pubkey,
            '_from_name':self.name,
            '_to_pub':another.pubkey,
            '_to_name':another.name,
            '_msg':msg,
        }
        # self.log(f'I am {self} packaging a message to {another}: {msg_d}')
        from komrade.backend.messages import Message
        
        msg_obj = Message(msg_d,from_whom=self,to_whom=another)
        
        # encrypt!
        # msg_obj.encrypt()

        return msg_obj

    # def compose_reply(self,msg,another):


    def seal_msg(self,msg_d):
        # make sure encrypted
        self.log('sealing msg!:',dict_format(msg_d))
        # msg_obj.encrypt(recursive=True)
        # return pure binary version of self's entire msg_d
        msg_b = pickle.dumps(msg_d)
        self.log('pickled!',msg_b)

        # encrypt by omega key
        msg_b_encr = self.omega_key.encrypt(msg_b)
        self.log('final seal:',msg_b_encr)

        return msg_b_encr

    def unseal_msg(self,msg_b_encr,from_whom=None,to_whom=None):
        # default to assumption that I am the recipient
        if not to_whom: to_whom=self
        # decrypt by omega
        msg_b = self.omega_key.decrypt(msg_b_encr)
        # unpackage from transmission
        msg_d = pickle.loads(msg_b)
        # get message obj
        # print('unsealed msg:',msg_d)
        from komrade.backend.messages import Message
        msg_obj = Message(msg_d,from_whom=from_whom,to_whom=to_whom)
        # decrypt msg
        return msg_obj


    def __repr__(self):
        clsname=(type(self)).__name__
        name = clsname+' '+self.name if self.name!=clsname else clsname
        keystr='+'.join(self.top_keys)
        # if self.pubkey:
        if False:
            pubk=self.pubkey_b64.decode()
            pubk=pubk[-5:]
            pubk = f' ({pubk})'# if pubk else ''
        else:
            pubk = ''
        return f'[{name}]{pubk} ({keystr})'

    def locate_an_operator(self,name):
        if name == OPERATOR_NAME:
            return TheOperator()
        if name == TELEPHONE_NAME:
            return TheTelephone()
        return Caller(name)


  
        # return resp_msg_obj

    def route(self,msg_obj):
        data,route = msg_obj.data, msg_obj.route
        if not hasattr(self,route) or route not in self.ROUTES:
            raise KomradeException('route not valid!')
        
        # route it!
        func = getattr(self,route)
        new_data = func(**data)
        self.log('got back from route func <-',new_data)

        # return the other way
        self.log('message was sent this way:',msg_obj)
        msg_obj.mark_return_to_sender(new_msg=new_data)
        self.log('now it\'s going the other way:',msg_obj)

        # if not decrypted
        if not msg_obj.is_encrypted:
            msg_obj.encrypt()

        return msg_obj


    def route_msg(self,msg_obj):
        # decrypt
        self.log('got msg_obj!',msg_obj)
        if msg_obj.is_encrypted:
            msg_obj.decrypt()
        # are there instructions for us?
        if msg_obj.route:
            # get result from routing
            self.log(f'routing msg to self.{msg_obj.route}(**{dict_format(msg_obj.data)})')
            return self.route(msg_obj)

        # can we pass the buck on?
        elif msg_obj.has_embedded_msg:
            embedded_msg = msg_obj.msg
            embedded_recipient = embedded_msg.to_whom

            # whew, then we can make someone else take the phone
            self.log(f'passing msg onto {embedded_recipient} ...')
            return embedded_recipient.route_msg(embedded_msg)

        # ???
        self.log('what do I do? giving this back to you...')
        return msg_obj
    



    def ring_ring(self,msg,to_whom,get_resp_from=None):
        # ring ring
        self.log(f'''
        ring ring!
        I am {self}.
        I have been told to pass onto {to_whom},
        by way of function {get_resp_from},
        the following msg:
        {dict_format(msg,tab=5)}
        ''')
        
        # get encr msg obj
        msg_obj = self.compose_msg_to(
            msg,
            to_whom
        )
        self.log(f'ring ring! here is the message object I made, to send to {to_whom}: {msg_obj}')
        
        # encrypting
        msg_obj.encrypt()
        # get pure encrypted binary, sealed
        #msg_sealed = self.seal_msg(msg_obj)
        
        # pass onto next person...
        if not get_resp_from: get_resp_from=to_whom.ring_ring
        resp_msg_obj = get_resp_from(msg_obj.msg_d)
        self.log('resp_msg_obj <-',resp_msg_obj)

        if resp_msg_obj.is_encrypted:
            resp_msg_obj.decrypt()
        

        # route back?
        route_result = self.route_msg(resp_msg_obj)
        self.log('route_result 2?',route_result)
        return route_result

        # decrypt?
        # from komrade.backend.messages import Message
        # if type(route_result)==Message:
            # if route_result.is_encrypted:
                # route_result.decrypt()

        # return route_result








    def pronto_pronto(self, msg_obj):
        self.log(f'''
        pronto pronto!
        >> {msg_obj}
        ''')

        route_response = self.route_msg(msg_obj)
        self.log('route_response',route_response)
        # set this to be the new msg
        #msg_obj.msg = msg_obj.msg_d['_msg'] = response
        #self.log('what msg_obj looks like now:',msg_obj)

        # send new content back
        resp_msg_obj = msg_obj.to_whom.compose_msg_to(
            route_response,
            msg_obj.from_whom
        )
        self.log('resp_msg_obj',resp_msg_obj)
        
        # re-encrypt
        resp_msg_obj.encrypt()
        self.log(f're-encrypted: {resp_msg_obj}')
        
        # pass msg back the chain
        return resp_msg_obj
        