from typing import OrderedDict


def insert_into_ordered_dict(odict, value, key=None, before=None, after=None):
    """
    Return copy of the input OrderedDict with a value inserted at a specific location.

    Does not modify input OrderedDict

    If key is specified, it must be a string and it must not already be present as a key
    in the input OrderedDict. It will be used as the key corresponding
    to the provided value in the resulting OrderedDict.  If None, the key will be
    the integer index of the insertion location.

    before and after may be strings or integer indices and they can be set the value
    of an existing key in the OrderedDict that the new value should be instered before
    or after.
    """
    # Validation
    if key is not None:
        if not isinstance(key, str):
            raise ValueError("key argument must be a string or None")
        if key in odict:
            raise ValueError("Provided key already present in dictionary")

    if before is not None and after is not None:
        raise ValueError("before and after may not both be specified")

    keys = list(odict)
    if before is None and after is None:
        insert_index = len(odict)
    elif before is not None:
        if isinstance(before, int):
            before_index = before
        else:
            before_index = keys.index(before)
        insert_index = before_index
    else:  # after is not None:
        if isinstance(after, int):
            after_index = after
        else:
            after_index = keys.index(after)
        insert_index = after_index + 1

    # User insert index as name if none provided
    if key is None:
        key = insert_index

    items = list(odict.items())
    items.insert(insert_index, (key, value))

    # Replace all integer names with index to avoid overwriting
    items = [(k if isinstance(k, str) else i, v) for i, (k, v) in enumerate(items)]
    return OrderedDict(items)