SERIALIZABLE_TYPES = (dict, list, tuple, set, bool, type(None), int, str, bytes)


def is_dict_type(value):
    """Treat any dict, MergeDict, MultiDict instance as dict type"""
    # Check by class name to avoid importing Django MergeDict or
    # Werkzeug MultiDict
    return isinstance(value, dict) or value.__class__.__name__ in (
        "MergeDict",
        "MultiDict",
    )


def partial_prepare(
    strategy, backend, next_step, user=None, social=None, *args, **kwargs
):
    kwargs.update(
        {
            "response": kwargs.get("response") or {},
            "details": kwargs.get("details") or {},
            "username": kwargs.get("username"),
            "uid": kwargs.get("uid"),
            "is_new": kwargs.get("is_new") or False,
            "new_association": kwargs.get("new_association") or False,
            "user": (hasattr(user, "id") and user.id) or None,
            "social": (social and {"provider": social.provider, "uid": social.uid})
            or None,
        }
    )

    clean_args = [strategy.to_session_value(val) for val in args]

    # Clean any MergeDict data type from the values
    clean_kwargs = {}
    for name, value in kwargs.items():
        value = dict(value) if is_dict_type(value) else value
        if isinstance(value, SERIALIZABLE_TYPES):
            clean_kwargs[name] = strategy.to_session_value(value)

    return strategy.storage.partial.prepare(
        backend.name, next_step, {"args": clean_args, "kwargs": clean_kwargs}
    )


def partial_store(
    strategy, backend, next_step, user=None, social=None, *args, **kwargs
):
    partial = partial_prepare(
        strategy, backend, next_step, user=user, social=social, *args, **kwargs
    )
    return strategy.storage.partial.store(partial)


def partial_load(strategy, token):
    partial = strategy.storage.partial.load(token)

    if partial:
        args = partial.args
        kwargs = partial.kwargs.copy()
        user = kwargs.get("user")
        social = kwargs.get("social")

        if isinstance(social, dict):
            kwargs["social"] = strategy.storage.user.get_social_auth(**social)

        if user:
            kwargs["user"] = strategy.storage.user.get_user(user)

        partial.args = [strategy.from_session_value(val) for val in args]
        partial.kwargs = {
            key: strategy.from_session_value(val) for key, val in kwargs.items()
        }
    return partial
