from six.moves.urllib.parse import quote_plus

from swimlane.core.cache import check_cache
from swimlane.core.cursor import PaginatedCursor
from swimlane.core.resolver import SwimlaneResolver
from swimlane.core.resources.usergroup import Group, User
from swimlane.utils import one_of_keyword_only


class GroupListCursor(SwimlaneResolver, PaginatedCursor):
    """Handles retrieval and pagination of group list endpoint"""

    def __init__(self, swimlane, limit=None):
        SwimlaneResolver.__init__(self, swimlane)
        PaginatedCursor.__init__(self, limit)

    def _parse_raw_element(self, raw_element):
        return Group(self._swimlane, raw_element)

    def _retrieve_raw_elements(self, page):
        response = self._swimlane.request(
            'get',
            'groups',
            params={
                'size': self.page_size,
                'pageNumber': page
            }
        )
        return response.json().get('items', [])


class GroupAdapter(SwimlaneResolver):
    """Handles retrieval of Swimlane Group resources"""

    def list(self, limit=None):
        """Retrieve list of all groups

        Returns:
            :class:`list` of :class:`~swimlane.core.resources.usergroup.Group`: List of all Groups
        Raises:
            ValueError: If limit is not of type integer or None
        """

        if (isinstance(limit, int) and limit > 0) or limit is None:
            return GroupListCursor(swimlane=self._swimlane, limit=limit)

        raise ValueError('Limit should be a positive whole number greater than 0')

    @check_cache(Group)
    @one_of_keyword_only('id', 'name')
    def get(self, key, value):
        """Retrieve single group record by id or name

        Supports resource cache

        Keyword Args:
            id (str): Full Group ID
            name (str): Group name

        Raises:
            TypeError: Unexpected or more than one keyword argument provided
            ValueError: No matching group found based on provided inputs
            ValueError: The lookup value is empty or None

        Returns:
            Group: Group instance matching provided inputs
        """
        if not value:
            raise ValueError('The value provided for the key "{0}" cannot be empty or None'.format(key))

        if key == 'id':
            response = self._swimlane.request('get', 'groups/{}'.format(value))
            return Group(self._swimlane, response.json())

        else:
            response = self._swimlane.request('get', 'groups/lookup?name={}'.format(value))
            matched_groups = response.json()

            for group_data in matched_groups:
                if group_data.get('name') == value:
                    return Group(self._swimlane, group_data)

            raise ValueError('Unable to find group with name "{}"'.format(value))


class UserListCursor(SwimlaneResolver, PaginatedCursor):
    """Handles retrieval and pagination for user list endpoint"""

    def __init__(self, swimlane, limit=None):
        SwimlaneResolver.__init__(self, swimlane)
        PaginatedCursor.__init__(self, limit)

    def _parse_raw_element(self, raw_element):
        return User(self._swimlane, raw_element)

    def _retrieve_raw_elements(self, page):
        response = self._swimlane.request(
            'get',
            'user',
            params={
                'size': self.page_size,
                'pageNumber': page
            }
        )
        return response.json().get('items', [])


class UserAdapter(SwimlaneResolver):
    """Handles retrieval of Swimlane User resources"""

    def list(self, limit=None):
        """Retrieve all users

        Returns:
            :class:`UserListCursor`: Paginated cursor yielding :class:`User` instances

        Raises:
            ValueError: If limit is not of type integer or None
        """

        if (isinstance(limit, int) and limit > 0) or limit is None:
            return UserListCursor(swimlane=self._swimlane, limit=limit)

        raise ValueError('Limit should be a positive whole number greater than 0')

    @check_cache(User)
    @one_of_keyword_only('id', 'display_name')
    def get(self, arg, value):
        """Retrieve single user record by id or username

        Warnings:
            User display names are not unique. If using `display_name`, method will fail if multiple Users are returned
            with the same display name

        Keyword Args:
            id (str): Full User ID
            display_name (str): User display name

        Returns:
            User: User instance matching provided inputs

        Raises:
            TypeError: Unexpected or more than one keyword argument provided
            ValueError: No matching user found based on provided inputs, or multiple Users with same display name
            ValueError: The lookup value is empty or None
        """
        if not value:
            raise ValueError('The value provided for the key "{0}" cannot be empty or None'.format(arg))

        if arg == 'id':
            response = self._swimlane.request('get', 'user/{}'.format(value))

            try:
                user_data = response.json()
            except ValueError:
                raise ValueError('Unable to find user with ID "{}"'.format(value))

            return User(self._swimlane, user_data)

        else:
            response = self._swimlane.request('get', 'user/search?query={}'.format(quote_plus(value)))
            matched_users = response.json()

            # Display name not unique, fail if multiple users share the same target display name
            target_matches = []

            for user_data in matched_users:
                user_display_name = user_data.get('displayName')
                if user_display_name == value:
                    target_matches.append(user_data)

            # No matches
            if not target_matches:
                raise ValueError('Unable to find user with display name "{}"'.format(value))

            # Multiple matches
            if len(target_matches) > 1:
                raise ValueError('Multiple users returned with display name "{}". Matching user IDs: {}'.format(
                    value,
                    ', '.join(['"{}"'.format(r['id']) for r in target_matches])
                ))

            return User(self._swimlane, target_matches[0])
