# pipelines.metadata_revision — DIMA 1.0.0 documentation

# Source code for pipelines.metadata_revision

    import sys
    import os

    try:
        thisFilePath = os.path.abspath(__file__)
    except NameError:
        print("Error: __file__ is not available. Ensure the script is being run from a file.")
        print("[Notice] Path to DIMA package may not be resolved properly.")
        thisFilePath = os.getcwd()  # Use current directory or specify a default

    dimaPath = os.path.normpath(os.path.join(thisFilePath, "..",'..'))  # Move up to project root

    if dimaPath not in sys.path:  # Avoid duplicate entries
        sys.path.append(dimaPath)

    import h5py
    import yaml
    import src.hdf5_ops as hdf5_ops

    def load_yaml(review_yaml_file):
        with open(review_yaml_file, 'r') as stream:
            try:
                return yaml.load(stream, Loader=yaml.FullLoader)
            except yaml.YAMLError as exc:
                print(exc)
                return None

    def validate_yaml_dict(input_hdf5_file, yaml_dict):
        errors = []
        notes = []

        with h5py.File(input_hdf5_file, 'r') as hdf5_file:
            # 1. Check for valid object names
            for key in yaml_dict:
                if key not in hdf5_file:
                    error_msg = f"Error: {key} is not a valid object's name in the HDF5 file."
                    print(error_msg)
                    errors.append(error_msg)

            # 2. Confirm metadata dict for each object is a dictionary
            for key, meta_dict in yaml_dict.items():
                if not isinstance(meta_dict, dict):
                    error_msg = f"Error: Metadata for {key} should be a dictionary."
                    print(error_msg)
                    errors.append(error_msg)
                else:
                    if 'attributes' not in meta_dict:
                        warning_msg = f"Warning: No 'attributes' in metadata dict for {key}."
                        print(warning_msg)
                        notes.append(warning_msg)

            # 3. Verify update, append, and delete operations are well specified
            for key, meta_dict in yaml_dict.items():
                attributes = meta_dict.get("attributes", {})

                for attr_name, attr_value in attributes.items():
                    # Ensure the object exists before accessing attributes
                    if key in hdf5_file:
                        hdf5_obj_attrs = hdf5_file[key].attrs  # Access object-specific attributes

                        if attr_name in hdf5_obj_attrs:
                            # Attribute exists: it can be updated or deleted
                            if isinstance(attr_value, dict) and "delete" in attr_value:
                                note_msg = f"Note: '{attr_name}' in {key} may be deleted if 'delete' is set as true."
                                print(note_msg)
                                notes.append(note_msg)
                            else:
                                note_msg = f"Note: '{attr_name}' in {key} will be updated."
                                print(note_msg)
                                notes.append(note_msg)
                        else:
                            # Attribute does not exist: it can be appended or flagged as an invalid delete
                            if isinstance(attr_value, dict) and "delete" in attr_value:
                                error_msg = f"Error: Cannot delete non-existent attribute '{attr_name}' in {key}."
                                print(error_msg)
                                errors.append(error_msg)
                            else:
                                note_msg = f"Note: '{attr_name}' in {key} will be appended."
                                print(note_msg)
                                notes.append(note_msg)
                    else:
                        error_msg = f"Error: '{key}' is not a valid object in the HDF5 file."
                        print(error_msg)
                        errors.append(error_msg)

        return len(errors) == 0, errors, notes

    def update_hdf5_file_with_review(input_hdf5_file, review_yaml_file):

        """
        Updates, appends, or deletes metadata attributes in an HDF5 file based on a provided YAML dictionary.

        Parameters:
        -----------
        input_hdf5_file : str
            Path to the HDF5 file.

        yaml_dict : dict
            Dictionary specifying objects and their attributes with operations. Example format:
            {
                "object_name": { "attributes" : "attr_name": { "value": attr_value,
                                                               "delete": true | false
                                                }
                                }
            }
        """
        yaml_dict = load_yaml(review_yaml_file)

        success, errors, notes = validate_yaml_dict(input_hdf5_file,yaml_dict)
        if not success:
            raise ValueError(f"Review yaml file {review_yaml_file} is invalid. Validation errors: {errors}")

        # Initialize HDF5 operations manager
        DataOpsAPI = hdf5_ops.HDF5DataOpsManager(input_hdf5_file)
        DataOpsAPI.load_file_obj()

        # Iterate over each object in the YAML dictionary
        for obj_name, attr_dict in yaml_dict.items():
            # Prepare dictionaries for append, update, and delete actions
            append_dict = {}
            update_dict = {}
            delete_dict = {}

            if not obj_name in DataOpsAPI.file_obj:
                continue  # Skip if the object does not exist

            # Iterate over each attribute in the current object
            for attr_name, attr_props in attr_dict['attributes'].items():
                if not isinstance(attr_props, dict):
                    #attr_props = {'value': attr_props}
                    # Check if the attribute exists (for updating)
                    if attr_name in DataOpsAPI.file_obj[obj_name].attrs:
                        update_dict[attr_name] = attr_props
                    # Otherwise, it's a new attribute to append
                    else:
                        append_dict[attr_name] = attr_props
                else:
                    # Check if the attribute is marked for deletion
                    if attr_props.get('delete', False):
                        delete_dict[attr_name] = attr_props

            # Perform a single pass for all three operations
            if append_dict:
                DataOpsAPI.append_metadata(obj_name, append_dict)
            if update_dict:
                DataOpsAPI.update_metadata(obj_name, update_dict)
            if delete_dict:
                DataOpsAPI.delete_metadata(obj_name, delete_dict)

        # Close hdf5 file
        DataOpsAPI.unload_file_obj()
        # Regenerate yaml snapshot of updated HDF5 file
        output_yml_filename_path = hdf5_ops.serialize_metadata(input_hdf5_file)
        print(f'{output_yml_filename_path} was successfully regenerated from the updated version of{input_hdf5_file}')

    def count(hdf5_obj,yml_dict):
        print(hdf5_obj.name)
        if isinstance(hdf5_obj,h5py.Group) and len(hdf5_obj.name.split('/')) <= 4:
            obj_review = yml_dict[hdf5_obj.name]
            additions = [not (item in hdf5_obj.attrs.keys()) for item in obj_review['attributes'].keys()]
            count_additions = sum(additions)
            deletions = [not (item in obj_review['attributes'].keys()) for item in hdf5_obj.attrs.keys()]
            count_delections = sum(deletions)
            print('additions',count_additions, 'deletions', count_delections)

    if __name__ == "__main__":

        if len(sys.argv) < 4:
            print("Usage: python metadata_revision.py update <path/to/target_file.hdf5> <path/to/metadata_review_file.yaml>")
            sys.exit(1)

        if sys.argv[1] == 'update':
            input_hdf5_file = sys.argv[2]
            review_yaml_file = sys.argv[3]
            update_hdf5_file_with_review(input_hdf5_file, review_yaml_file)
        #run(sys.argv[2])