from typing import Any, Dict, List

from ..exceptions import DashboardException


class ListPaginator:
    # pylint: disable=W0102
    def __init__(self, offset: int, limit: int, sort: str, search: str,
                 input_list: List[Any], default_sort: str,
                 searchable_params: List[str] = [], sortable_params: List[str] = []):
        self.offset = offset
        if limit < -1:
            raise DashboardException(msg=f'Wrong limit value {limit}', code=400)
        self.limit = limit
        self.sort = sort
        self.search = search
        self.input_list = input_list
        self.default_sort = default_sort
        self.searchable_params = searchable_params
        self.sortable_params = sortable_params
        self.count = len(self.input_list)

    def get_count(self):
        return self.count

    def find_value(self, item: Dict[str, Any], key: str):
        # dot separated keys to lookup nested values
        keys = key.split('.')
        value = item
        for nested_key in keys:
            if nested_key in value:
                value = value[nested_key]
            else:
                return ''
        return value

    def list(self):
        end = self.offset + self.limit
        # '-1' is a special number to refer to all items in list
        if self.limit == -1:
            end = len(self.input_list)

        if not self.sort:
            self.sort = self.default_sort

        desc = self.sort[0] == '-'
        sort_by = self.sort[1:]

        if sort_by not in self.sortable_params:
            sort_by = self.default_sort[1:]

        # trim down by search
        trimmed_list = []
        if self.search:
            for item in self.input_list:
                for searchable_param in self.searchable_params:
                    value = self.find_value(item, searchable_param)
                    if isinstance(value, str):
                        if self.search in str(value):
                            trimmed_list.append(item)

        else:
            trimmed_list = self.input_list

        def sort(item):
            return self.find_value(item, sort_by)

        sorted_list = sorted(trimmed_list, key=sort, reverse=desc)
        self.count = len(sorted_list)
        for item in sorted_list[self.offset:end]:
            yield item
