from typing import Union
import inspect
from enum import Enum


def find_object_value_with_key_or_value(
    obj:object, needle:Union[str,object], 
    ignore_case=False, 
    throw_exception=False
):
    """Given a class or class instance, search the 
    attribute keys and values of the object for the given "needle" and return its corresponding value.

    NOTE: If a class if given then it must be instantiable using a default constructor (except of Enum classes).
    
    Args:
        obj: Class or class instance
        needle: Class attribute key or value to find in class instance 
        ignore_case: Ignore the key's case if True (needle must be a string)
        throw_exception: If true, throw an exception if the needle is not found, return None otherwise
    Return:
        Value of found attribute key or None if key/value is not found
    """
    if needle is None:
        return None 

    if inspect.isclass(obj) and not issubclass(obj, Enum):
        obj = obj()

    needle_lower = None
    if ignore_case and isinstance(needle, str):
        needle_lower = needle.lower()
    
    for key in dir(obj):
        value = getattr(obj, key)
        if (needle_lower is None and key == needle) or \
            (needle_lower is not None and key.lower() == needle_lower) or \
            (value == needle):
            return value

    if throw_exception:
        raise ValueError(f'{needle} not found in {obj}')
        
    return None