from collections import OrderedDict
from usaspending_api.search.filters.elasticsearch.naics import NaicsCodes
from usaspending_api.search.filters.elasticsearch.tas import TasCodes
from usaspending_api.search.filters.mixins.psc import PSCCodesMixin


def upper_case_dict_values(input_dict):
    for key in input_dict:
        if isinstance(input_dict[key], str):
            input_dict[key] = input_dict[key].upper()


def order_nested_filter_tree_object(nested_object):
    """
    Filter tree searches look a bit like:
        {
          "psc_codes": {
              "require": [["Service", "B", "B5"]],
              "exclude": [["Service", "B", "B5", "B502"]]
          }
        }
    The position of the values in the innermost lists is important.  Exclude them from sorting.
    """
    if not isinstance(nested_object, dict):
        return order_nested_object(nested_object)

    # Sort the keys and sort the outer list of "require" and "exclude" but do not recurse the inner lists.
    # Everything else is handled using the default sorting behavior.
    return OrderedDict(
        [
            (
                key,
                sorted(nested_object[key])
                if key in ("require", "exclude") and isinstance(nested_object[key], list)
                else order_nested_object(nested_object[key]),
            )
            for key in sorted(nested_object.keys())
        ]
    )


def order_nested_object(nested_object):
    """
    Simply recursively order the item. To be used for standardizing objects for JSON dumps
    """
    if isinstance(nested_object, list):
        if len(nested_object) > 0 and isinstance(nested_object[0], dict):
            # Lists of dicts aren't handled by python's sorted(), so we handle sorting manually
            sorted_subitems = []
            sort_dict = {}
            # Create a hash using keys & values
            for subitem in nested_object:
                hash_list = ["{}{}".format(key, subitem[key]) for key in sorted(list(subitem.keys()))]
                hash_str = "_".join(str(hash_list))
                sort_dict[hash_str] = order_nested_object(subitem)
            # Sort by the new hash
            for sorted_hash in sorted(list(sort_dict.keys())):
                sorted_subitems.append(sort_dict[sorted_hash])
            return sorted_subitems
        else:
            return sorted([order_nested_object(subitem) for subitem in nested_object])
    elif isinstance(nested_object, dict):
        # Filter tree values are positional and require special handling.  Some filter tree filters
        # support legacy lists so only perform special handling if the filter is the newer style dictionary.
        return OrderedDict(
            [
                (
                    key,
                    order_nested_filter_tree_object(nested_object[key])
                    if key in (NaicsCodes.underscore_name, PSCCodesMixin.underscore_name, TasCodes.underscore_name)
                    and isinstance(nested_object[key], dict)
                    else order_nested_object(nested_object[key]),
                )
                for key in sorted(nested_object.keys())
            ]
        )
    else:
        return nested_object

from collections import OrderedDict
from usaspending_api.search.filters.elasticsearch.naics import NaicsCodes
from usaspending_api.search.filters.elasticsearch.tas import TasCodes
from usaspending_api.search.filters.mixins.psc import PSCCodesMixin


def upper_case_dict_values(input_dict):
    for key in input_dict:
        if isinstance(input_dict[key], str):
            input_dict[key] = input_dict[key].upper()


def order_nested_filter_tree_object(nested_object):
    """
    Filter tree searches look a bit like:
        {
          "psc_codes": {
              "require": [["Service", "B", "B5"]],
              "exclude": [["Service", "B", "B5", "B502"]]
          }
        }
    The position of the values in the innermost lists is important.  Exclude them from sorting.
    """
    if not isinstance(nested_object, dict):
        return order_nested_object(nested_object)

    # Sort the keys and sort the outer list of "require" and "exclude" but do not recurse the inner lists.
    # Everything else is handled using the default sorting behavior.
    return OrderedDict(
        [
            (
                key,
                sorted(nested_object[key])
                if key in ("require", "exclude") and isinstance(nested_object[key], list)
                else order_nested_object(nested_object[key]),
            )
            for key in sorted(nested_object.keys())
        ]
    )


def order_nested_object(nested_object):
    """
    Simply recursively order the item. To be used for standardizing objects for JSON dumps
    """
    if isinstance(nested_object, list):
        if len(nested_object) > 0 and isinstance(nested_object[0], dict):
            # Lists of dicts aren't handled by python's sorted(), so we handle sorting manually
            sorted_subitems = []
            sort_dict = {}
            # Create a hash using keys & values
            for subitem in nested_object:
                hash_list = ["{}{}".format(key, subitem[key]) for key in sorted(list(subitem.keys()))]
                hash_str = "_".join(str(hash_list))
                sort_dict[hash_str] = order_nested_object(subitem)
            # Sort by the new hash
            for sorted_hash in sorted(list(sort_dict.keys())):
                sorted_subitems.append(sort_dict[sorted_hash])
            return sorted_subitems
        else:
            return sorted([order_nested_object(subitem) for subitem in nested_object])
    elif isinstance(nested_object, dict):
        # Filter tree values are positional and require special handling.  Some filter tree filters
        # support legacy lists so only perform special handling if the filter is the newer style dictionary.
        return OrderedDict(
            [
                (
                    key,
                    order_nested_filter_tree_object(nested_object[key])
                    if key in (NaicsCodes.underscore_name, PSCCodesMixin.underscore_name, TasCodes.underscore_name)
                    and isinstance(nested_object[key], dict)
                    else order_nested_object(nested_object[key]),
                )
                for key in sorted(nested_object.keys())
            ]
        )
    else:
        return nested_object

Filter tree searches look a bit like:         {           "psc_codes": {               "require": [["Service", "B", "B5"]],               "exclude": [["Service", "B", "B5", "B502"]]           }         }     The position of the values in the innermost lists is important.  Exclude them from sorting. # Sort the keys and sort the outer list of "require" and "exclude" but do not recurse the inner lists. # Everything else is handled using the default sorting behavior. Simply recursively order the item. To be used for standardizing objects for JSON dumps # Lists of dicts aren't handled by python's sorted(), so we handle sorting manually # Create a hash using keys & values # Sort by the new hash # Filter tree values are positional and require special handling.  Some filter tree filters # support legacy lists so only perform special handling if the filter is the newer style dictionary.