class IdentityManagementService(BaseIdentityManagementService):

    """
    A resource registry that stores identities of users and resources, including bindings of internal identities to external identities. Also stores metadata such as a user profile.a	A resource registry that stores identities of users and resources, including bindings of internal identities to external identities. Also stores metadata such as a user profile.a
    """

    def on_init(self):
        self.authentication = Authentication()

    def create_user_identity(self, user_identity=None):
        # Persist UserIdentity object and return object _id as OOI id
        user_id, version = self.clients.resource_registry.create(user_identity)
        return user_id

    def update_user_identity(self, user_identity=None):
        # Overwrite UserIdentity object
        self.clients.resource_registry.update(user_identity)

    def read_user_identity(self, user_id=""):
        # Read UserIdentity object with _id matching passed user id
        user_identity = self.clients.resource_registry.read(user_id)
        if not user_identity:
            raise NotFound("UserIdentity %s does not exist" % user_id)
        return user_identity

    def delete_user_identity(self, user_id=""):
        # Read and delete specified UserIdentity object
        user_identity = self.clients.resource_registry.read(user_id)
        if not user_identity:
            raise NotFound("UserIdentity %s does not exist" % user_id)
        self.clients.resource_registry.delete(user_id)

    def register_user_credentials(self, user_id="", credentials=None):
        # Create UserCredentials object
        credentials_obj_id, version = self.clients.resource_registry.create(credentials)
        # Create association with user identity object
        res = self.clients.resource_registry.create_association(user_id, AT.hasCredentials, credentials_obj_id)

    def unregister_user_credentials(self, user_id="", credentials_name=""):
        # Read UserCredentials
        objects, matches = self.clients.resource_registry.find_resources(
            RT.UserCredentials, None, credentials_name, False
        )
        if not objects or len(objects) == 0:
            raise NotFound("UserCredentials %s does not exist" % credentials_name)
        if len(objects) > 1:
            raise Conflict("Multiple UserCredentials objects found for subject %s" % credentials_name)
        user_credentials_id = objects[0]._id
        # Find and break association with UserIdentity
        assocs = self.clients.resource_registry.find_associations(user_id, AT.hasCredentials, user_credentials_id)
        if not assocs or len(assocs) == 0:
            raise NotFound(
                "UserIdentity to UserCredentials association for user id %s to credential %s does not exist"
                % (user_id, credentials_name)
            )
        association_id = assocs[0]._id
        self.clients.resource_registry.delete_association(association_id)
        # Delete the UserCredentials
        self.clients.resource_registry.delete(user_credentials_id)

    def create_user_info(self, user_id="", user_info=None):
        # Ensure UserInfo association does not already exist
        objects, assocs = self.clients.resource_registry.find_objects(user_id, AT.hasInfo, RT.UserInfo)
        if objects:
            raise Conflict("UserInfo already exists for user id %s" % (user_id))
        # Create UserInfo object
        user_info_id, version = self.clients.resource_registry.create(user_info)
        log.warn("user_info_id: %s" % user_info_id)
        # Create association with user identity object
        self.clients.resource_registry.create_association(user_id, AT.hasInfo, user_info_id)
        return user_info_id

    def update_user_info(self, user_info=None):
        # Overwrite UserInfo object
        self.clients.resource_registry.update(user_info)

    def read_user_info(self, user_info_id=""):
        # Read UserInfo object with _id matching passed user id
        user_info = self.clients.resource_registry.read(user_info_id)
        if not user_info:
            raise NotFound("UserInfo %s does not exist" % user_info_id)
        return user_info

    def delete_user_info(self, user_info_id=""):
        # Read UserInfo
        user_info = self.clients.resource_registry.read(user_info_id)
        if not user_info:
            raise NotFound("UserInfo %s does not exist" % user_info_id)
        # Find and break association with UserIdentity
        subjects, assocs = self.clients.resource_registry.find_subjects(RT.UserIdentity, AT.hasInfo, user_info_id)
        if not assocs:
            raise NotFound("UserIdentity to UserInfo association for user info id %s does not exist" % user_info_id)
        user_identity_id = subjects[0]._id

        assocs = self.clients.resource_registry.find_associations(user_identity_id, AT.hasInfo, user_info_id)
        if not assocs:
            raise NotFound("UserIdentity to UserInfo association for user info id %s does not exist" % user_info_id)
        association_id = assocs[0]._id

        self.clients.resource_registry.delete_association(association_id)
        # Delete the UserInfo
        self.clients.resource_registry.delete(user_info_id)

    def find_user_info_by_id(self, user_id=""):
        # Look up UserInfo via association with UserIdentity
        objects, assocs = self.clients.resource_registry.find_objects(user_id, AT.hasInfo, RT.UserInfo)
        if not objects:
            raise NotFound("UserInfo for user id %s does not exist" % user_id)
        user_info = objects[0]
        return user_info

    def find_user_info_by_name(self, name=""):
        objects, matches = self.clients.resource_registry.find_resources(RT.UserInfo, None, name, False)
        if not objects:
            raise NotFound("UserInfo with name %s does not exist" % name)
        if len(objects) > 1:
            raise Inconsistent("Multiple UserInfos with name %s exist" % name)
        return objects[0]

    def find_user_info_by_subject(self, subject=""):
        # Find UserCredentials
        objects, matches = self.clients.resource_registry.find_resources(RT.UserCredentials, None, subject, False)
        if not objects:
            raise NotFound("UserCredentials with subject %s does not exist" % subject)
        if len(objects) > 1:
            raise Inconsistent("Multiple UserCredentials with subject %s exist" % subject)
        user_credentials_id = objects[0]._id
        subjects, assocs = self.clients.resource_registry.find_subjects(
            RT.UserIdentity, AT.hasCredentials, user_credentials_id
        )
        if not subjects or len(subjects) == 0:
            raise NotFound("UserIdentity to UserCredentials association for subject %s does not exist" % subject)
        if len(subjects) > 1:
            raise Inconsistent("Multiple UserIdentity to UserCredentials associations for subject %s exist" % subject)
        user_identity_id = subjects[0]._id
        # Look up UserInfo via association with UserIdentity
        objects, assocs = self.clients.resource_registry.find_objects(user_identity_id, AT.hasInfo, RT.UserInfo)
        if not objects:
            raise NotFound("UserInfo for subject %s does not exist" % subject)
        if len(objects) > 1:
            raise Inconsistent("Multiple UserInfos for subject %s exist" % subject)
        user_info = objects[0]
        return user_info

    def signon(self, certificate="", ignore_date_range=False):
        # Check the certificate is currently valid
        if not ignore_date_range:
            if not self.authentication.is_certificate_within_date_range(certificate):
                raise BadRequest("Certificate expired or not yet valid")

        # Extract subject line
        attributes = self.authentication.decode_certificate(certificate)
        subject = attributes["subject"]
        valid_until = attributes["not_valid_after"]

        # Look for matching UserCredentials object
        objects, assocs = self.clients.resource_registry.find_resources(RT.UserCredentials, None, subject, True)
        if len(objects) > 1:
            raise Conflict("More than one UserCredentials object was found for subject %s" % subject)
        if len(assocs) > 1:
            raise Conflict("More than one UserIdentity object is associated with subject %s" % subject)
        if len(objects) == 1:
            # Known user, get UserIdentity object
            user_credentials_id = objects[0]
            log.warn("objects: %s" % str(objects))
            log.warn("user_credentials_id: %s" % user_credentials_id)
            subjects, assocs = self.clients.resource_registry.find_subjects(
                RT.UserIdentity, AT.hasCredentials, user_credentials_id
            )

            if len(subjects) == 0:
                raise Conflict(
                    "UserIdentity object with subject %s was previously created but is not associated with a UserIdentity object"
                    % subject
                )
            user_id = subjects[0]._id
            log.warn("subjects: %s" % str(subjects))
            log.warn("user_id: %s" % user_id)
            # Find associated UserInfo
            registered = True
            try:
                self.find_user_info_by_id(user_id)
            except NotFound:
                registered = False
            return user_id, valid_until, registered
        else:
            # New user.  Create UserIdentity and UserCredentials
            user_identity = IonObject("UserIdentity", {"name": subject})
            user_id = self.create_user_identity(user_identity)

            user_credentials = IonObject("UserCredentials", {"name": subject})
            self.register_user_credentials(user_id, user_credentials)
            return user_id, valid_until, False

    def create_resource_identity(self, resource_identity=None):
        # Persist ResourceIdentity object and return object _id as OOI id
        resource_identity_id, version = self.clients.resource_registry.create(resource_identity)
        return resource_identity_id

    def update_resource_identity(self, resource_identity=None):
        # Overwrite ResourceIdentity object
        self.clients.resource_registry.update(resource_identity)

    def read_resource_identity(self, resource_identity_id=""):
        # Read ResourceIdentity object with _id matching passed user id
        return self.clients.resource_registry.read(resource_identity_id)

    def delete_resource_identity(self, resource_identity_id=""):
        # Read and delete specified ResourceIdentity object
        resource_identity = self.clients.resource_registry.read(resource_identity_id)
        self.clients.resource_registry.delete(resource_identity_id)
class IdentityManagementService(BaseIdentityManagementService):

    """
    A resource registry that stores identities of users and resources, including bindings of internal identities to external identities. Also stores metadata such as a user profile.a	A resource registry that stores identities of users and resources, including bindings of internal identities to external identities. Also stores metadata such as a user profile.a
    """

    def on_init(self):
        self.authentication = Authentication()
    
    def create_actor_identity(self, actor_identity=None):
        # Persist ActorIdentity object and return object _id as OOI id
        user_id, version = self.clients.resource_registry.create(actor_identity)
        return user_id

    def update_actor_identity(self, actor_identity=None):
        # Overwrite ActorIdentity object
        self.clients.resource_registry.update(actor_identity)

    def read_actor_identity(self, user_id=''):
        # Read ActorIdentity object with _id matching passed user id
        actor_identity = self.clients.resource_registry.read(user_id)
        if not actor_identity:
            raise NotFound("ActorIdentity %s does not exist" % user_id)
        return actor_identity

    def delete_actor_identity(self, user_id=''):
        # Read and delete specified ActorIdentity object
        actor_identity = self.clients.resource_registry.read(user_id)
        if not actor_identity:
            raise NotFound("ActorIdentity %s does not exist" % user_id)
        self.clients.resource_registry.delete(user_id)

    def find_actor_identity_by_name(self, name=''):
        """Return the ActorIdentity object whose name attribute matches the passed value.

        @param name    str
        @retval user_info    ActorIdentity
        @throws NotFound    failed to find ActorIdentity
        @throws Inconsistent    Multiple ActorIdentity objects matched name
        """
        objects, matches = self.clients.resource_registry.find_resources(RT.ActorIdentity, None, name, False)
        if not objects:
            raise NotFound("ActorIdentity with name %s does not exist" % name)
        if len(objects) > 1:
            raise Inconsistent("Multiple ActorIdentity objects with name %s exist" % name)
        return objects[0]


    def register_user_credentials(self, user_id='', credentials=None):
        # Create UserCredentials object
        credentials_obj_id, version = self.clients.resource_registry.create(credentials)
        # Create association with user identity object
        res = self.clients.resource_registry.create_association(user_id, PRED.hasCredentials, credentials_obj_id)

    def unregister_user_credentials(self, user_id='', credentials_name=''):
        # Read UserCredentials
        objects, matches = self.clients.resource_registry.find_resources(RT.UserCredentials, None, credentials_name, False)
        if not objects or len(objects) == 0:
            raise NotFound("UserCredentials %s does not exist" % credentials_name)
        if len(objects) > 1:
            raise Conflict("Multiple UserCredentials objects found for subject %s" % credentials_name)
        user_credentials_id = objects[0]._id
        # Find and break association with ActorIdentity
        assocs = self.clients.resource_registry.find_associations(user_id, PRED.hasCredentials, user_credentials_id)
        if not assocs or len(assocs) == 0:
            raise NotFound("ActorIdentity to UserCredentials association for user id %s to credential %s does not exist" % (user_id,credentials_name))
        association_id = assocs[0]._id
        self.clients.resource_registry.delete_association(association_id)
        # Delete the UserCredentials
        self.clients.resource_registry.delete(user_credentials_id)

    def create_user_info(self, user_id="", user_info=None):
        # Ensure UserInfo association does not already exist
        objects, assocs = self.clients.resource_registry.find_objects(user_id, PRED.hasInfo, RT.UserInfo)
        if objects:
            raise Conflict("UserInfo already exists for user id %s" % (user_id))
        # Create UserInfo object
        user_info_id, version = self.clients.resource_registry.create(user_info)
        # Create association with user identity object
        self.clients.resource_registry.create_association(user_id, PRED.hasInfo, user_info_id)
        return user_info_id

    def update_user_info(self, user_info=None):
        # Overwrite UserInfo object
        self.clients.resource_registry.update(user_info)

    def read_user_info(self, user_info_id=''):
        # Read UserInfo object with _id matching passed user id
        user_info = self.clients.resource_registry.read(user_info_id)
        if not user_info:
            raise NotFound("UserInfo %s does not exist" % user_info_id)
        return user_info

    def delete_user_info(self, user_info_id=''):
        # Read UserInfo
        user_info = self.clients.resource_registry.read(user_info_id)
        if not user_info:
            raise NotFound("UserInfo %s does not exist" % user_info_id)
        # Find and break association with ActorIdentity
        subjects, assocs = self.clients.resource_registry.find_subjects(RT.ActorIdentity, PRED.hasInfo, user_info_id)
        if not assocs:
            raise NotFound("ActorIdentity to UserInfo association for user info id %s does not exist" % user_info_id)
        actor_identity_id = subjects[0]._id

        assocs = self.clients.resource_registry.find_associations(actor_identity_id, PRED.hasInfo, user_info_id)
        if not assocs:
            raise NotFound("ActorIdentity to UserInfo association for user info id %s does not exist" % user_info_id)
        association_id = assocs[0]._id
        
        self.clients.resource_registry.delete_association(association_id)
        # Delete the UserInfo
        self.clients.resource_registry.delete(user_info_id)

    def find_user_info_by_id(self, user_id=''):
        # Look up UserInfo via association with ActorIdentity
        objects, assocs = self.clients.resource_registry.find_objects(user_id, PRED.hasInfo, RT.UserInfo)
        if not objects:
            raise NotFound("UserInfo for user id %s does not exist" % user_id)
        user_info = objects[0]
        return user_info

    def find_user_info_by_name(self, name=''):
        objects, matches = self.clients.resource_registry.find_resources(RT.UserInfo, None, name, False)
        if not objects:
            raise NotFound("UserInfo with name %s does not exist" % name)
        if len(objects) > 1:
            raise Inconsistent("Multiple UserInfo objects with name %s exist" % name)
        return objects[0]

    def find_user_info_by_subject(self, subject=''):
        # Find UserCredentials
        objects, matches = self.clients.resource_registry.find_resources(RT.UserCredentials, None, subject, False)      
        if not objects:
            raise NotFound("UserCredentials with subject %s does not exist" % subject)
        if len(objects) > 1:
            raise Inconsistent("Multiple UserCredentials with subject %s exist" % subject)
        user_credentials_id = objects[0]._id
        subjects, assocs = self.clients.resource_registry.find_subjects(RT.ActorIdentity, PRED.hasCredentials, user_credentials_id)
        if not subjects or len(subjects) == 0:
            raise NotFound("ActorIdentity to UserCredentials association for subject %s does not exist" % subject)
        if len(subjects) > 1:
            raise Inconsistent("Multiple ActorIdentity to UserCredentials associations for subject %s exist" % subject)
        actor_identity_id = subjects[0]._id
        # Look up UserInfo via association with ActorIdentity
        objects, assocs = self.clients.resource_registry.find_objects(actor_identity_id, PRED.hasInfo, RT.UserInfo)
        if not objects:
            raise NotFound("UserInfo for subject %s does not exist" % subject)
        if len(objects) > 1:
            raise Inconsistent("Multiple UserInfos for subject %s exist" % subject)
        user_info = objects[0]
        return user_info

    def signon(self, certificate='', ignore_date_range=False):
        log.debug("Signon with certificate:\n%s" % certificate)
        # Check the certificate is currently valid
        if not ignore_date_range:
            if not self.authentication.is_certificate_within_date_range(certificate):
                raise BadRequest("Certificate expired or not yet valid")

        # Extract subject line
        attributes = self.authentication.decode_certificate(certificate)
        subject = attributes["subject"]
        valid_until_str = attributes["not_valid_after"]
        log.debug("Signon request for subject %s with string valid_until %s" % (subject, valid_until_str))
        valid_until_tuple = time.strptime(valid_until_str, "%b %d %H:%M:%S %Y %Z")
        valid_until = str(int(time.mktime(valid_until_tuple)) * 1000)

        # Look for matching UserCredentials object
        objects, assocs = self.clients.resource_registry.find_resources(RT.UserCredentials, None, subject, True)
        if len(objects) > 1:
            raise Conflict("More than one UserCredentials object was found for subject %s" % subject)
        if len(assocs) > 1:
            raise Conflict("More than one ActorIdentity object is associated with subject %s" % subject)
        if len(objects) == 1:
            log.debug("Signon known subject %s" % (subject))
            # Known user, get ActorIdentity object
            user_credentials_id = objects[0]
            subjects, assocs = self.clients.resource_registry.find_subjects(RT.ActorIdentity, PRED.hasCredentials, user_credentials_id)

            if len(subjects) == 0:
                raise Conflict("ActorIdentity object with subject %s was previously created but is not associated with a ActorIdentity object" % subject)
            user_id = subjects[0]._id
            # Find associated UserInfo
            registered = True
            try:
                self.find_user_info_by_id(user_id)
            except NotFound:
                registered = False
            log.debug("Signon returning user_id, valid_until, registered: %s, %s, %s" % (user_id, valid_until, str(registered)))
            return user_id, valid_until, registered
        else:
            log.debug("Signon new subject %s" % (subject))
            # New user.  Create ActorIdentity and UserCredentials
            actor_identity = IonObject("ActorIdentity", {"name": subject})
            user_id = self.create_actor_identity(actor_identity)

            user_credentials = IonObject("UserCredentials", {"name": subject})
            self.register_user_credentials(user_id, user_credentials)
            log.debug("Signon returning user_id, valid_until, registered: %s, %s, False" % (user_id, valid_until))
            return user_id, valid_until, False
        

    def create_resource_identity(self, resource_identity=None):
        # Persist ResourceIdentity object and return object _id as OOI id
        resource_identity_id, version = self.clients.resource_registry.create(resource_identity)
        return resource_identity_id

    def update_resource_identity(self, resource_identity=None):
        # Overwrite ResourceIdentity object
        self.clients.resource_registry.update(resource_identity)

    def read_resource_identity(self, resource_identity_id=''):
        # Read ResourceIdentity object with _id matching passed user id
        return self.clients.resource_registry.read(resource_identity_id)

    def delete_resource_identity(self, resource_identity_id=''):
        # Read and delete specified ResourceIdentity object
        resource_identity = self.clients.resource_registry.read(resource_identity_id)
        self.clients.resource_registry.delete(resource_identity_id)