from typing import Callable
from typing import List
from typing import OrderedDict


def order_json(data, sort_fn: Callable[[List[str]], List[str]] = sorted):
    """Sort json to a consistent order.

    When you hash json that has the some content but is different orders you get
    different fingerprints.

    .. code:: python
        >>> hashlib.sha1(json.dumps({'a': 12, 'b':14}).encode('utf-8')).hexdigest()
        ... '647aa7508f72ece3f8b9df986a206d95fd9a2caf'
        >>> hashlib.sha1(json.dumps({'b': 14, 'a':12}).encode('utf-8')).hexdigest()
        ... 'a22215982dc0e53617be08de7ba9f1a80d232b23'

    Note:
        According to json.org `An object is an unordered set of name/value pairs`. This
        means that the maps (dicts in python) should not contain semantic meaning in the
        order of the key value pairs. This means we are allowed to sort these maps without
        breaking something a user is doing in their custom config

        Similarly `An array is an ordered collection of values` this means that we should
        not sort the lists because that might break the way that a user is using a custom
        list in the json.

    :param data: The json data.
    :param sort_fn: A function that sorts the keys of the dictionary.
    :returns:
        collections.OrderedDict: The data in a consistent order (keys sorted alphabetically).
    """
    new = OrderedDict()
    for key in sort_fn(data.keys()):
        value = data[key]
        # If the value is another map recursively sort that
        if isinstance(value, dict):
            value = order_json(value)
        # If the value is a list, recursively sort any maps that are in it.
        elif isinstance(value, list):
            value = [order_json(v) if isinstance(v, dict) else v for v in value]
        new[key] = value
    return new