"""Python API for user profiles.

Profile information includes a student's demographic information and preferences,
but does NOT include basic account information such as username, password, and
email address.

"""


from user_api.models import User, UserProfile, UserPreference
from user_api.helpers import intercept_errors


class ProfileRequestError(Exception):
    """ The request to the API was not valid. """
    pass


class ProfileUserNotFound(ProfileRequestError):
    """ The requested user does not exist. """
    pass


class ProfileInvalidField(ProfileRequestError):
    """ The proposed value for a field is not in a valid format. """

    def __init__(self, field, value):
        self.field = field
        self.value = value

    def __str__(self):
        return u"Invalid value '{value}' for profile field '{field}'".format(
            value=self.value,
            field=self.field
        )


class ProfileInternalError(Exception):
    """ An error occurred in an API call. """
    pass


FULL_NAME_MAX_LENGTH = 255


@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def profile_info(username):
    """Retrieve a user's profile information.

    Searches either by username or email.

    At least one of the keyword args must be provided.

    Args:
        username (unicode): The username of the account to retrieve.

    Returns:
        dict: If profile information was found.
        None: If the provided username did not match any profiles.

    """
    try:
        profile = UserProfile.objects.get(user__username=username)
    except UserProfile.DoesNotExist:
        return None

    profile_dict = {
        u'username': profile.user.username,
        u'email': profile.user.email,
        u'full_name': profile.name,
    }

    return profile_dict


@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def update_profile(username, full_name=None):
    """Update a user's profile.

    Args:
        username (unicode): The username associated with the account.

    Keyword Args:
        full_name (unicode): If provided, set the user's full name to this value.

    Returns:
        None

    Raises:
        ProfileRequestError: If there is no profile matching the provided username.

    """
    try:
        profile = UserProfile.objects.get(user__username=username)
    except UserProfile.DoesNotExist:
        raise ProfileUserNotFound

    if full_name is not None:
        name_length = len(full_name)
        if name_length > FULL_NAME_MAX_LENGTH or name_length == 0:
            raise ProfileInvalidField("full_name", full_name)
        else:
            profile.update_name(full_name)


@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def preference_info(username):
    """Retrieve information about a user's preferences.

    Args:
        username (unicode): The username of the account to retrieve.

    Returns:
        dict: Empty if there is no user

    """
    preferences = UserPreference.objects.filter(user__username=username)

    preferences_dict = {}
    for preference in preferences:
        preferences_dict[preference.key] = preference.value

    return preferences_dict


@intercept_errors(ProfileInternalError, ignore_errors=[ProfileRequestError])
def update_preferences(username, **kwargs):
    """Update a user's preferences.

    Sets the provided preferences for the given user.

    Args:
        username (unicode): The username of the account to retrieve.

    Keyword Args:
        **kwargs (unicode): Arbitrary key-value preference pairs

    Returns:
        None

    Raises:
        ProfileUserNotFound

    """
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        raise ProfileUserNotFound
    else:
        for key, value in kwargs.iteritems():
            UserPreference.set_preference(user, key, value)