"""
server_side_datatables.py
=========================
"""

from collections import namedtuple, defaultdict
from lib.Config import Configuration


class ServerSideDataTable(object):
    """
    This class holds all the logic for enabling and handling server side DataTables within the application

    :param request: The post (or get) parameters send by the client side of DataTables
    :type request: flask.request
    :param backend: Handler to the backend
    :type backend: DatabasePluginBase
    """

    def __init__(self, request, backend, additional_filters=None):
        self.request_values = request.values

        self.backend = backend

        self.additional_filters = additional_filters

        self.columns, self.ordering, self.results, self.fields, self.data_length = (
            None,
            None,
            None,
            None,
            None,
        )

        self.total, self.total_filtered, self.current_draw = 0, 0, 0

        self.filtered = {}

        self.sort = []

        self._pre_fetch_processing()

    def output_result(self):
        retdata = {
            "draw": int(self.current_draw),
            "recordsTotal": int(self.total),
            "recordsFiltered": int(self.total_filtered),
            "data": self.results,
        }

        return retdata

    def _pre_fetch_processing(self):
        """
        Method to provision the necessary variables for the correct retrieval of the results from the MongoDB Database
        """

        ALLOWED_COLUMNS = {
            "blank",
            "id",
            "cvss",
            "cvss3",
            "cvss4",
            "summary",
            "lastModified",
            "published",
        }

        self.current_draw = int(self.request_values["draw"])

        self.data_length = self.__data_dimension()

        self.columns, self.ordering = self.backend.datatables_data_columns_ordering(
            self.request_values
        )

        self.fields = defaultdict(int)

        # Validate columns before they are used for sorting/filtering/projection
        for each in self.columns:
            field = self.columns[each]["data"]

            if field not in ALLOWED_COLUMNS:
                raise ValueError("Invalid column requested")

            # Frontend-only synthetic column; not stored in MongoDB
            if field == "blank":
                continue

            self.fields[field] = 1

        self.sort = self.backend.datatables_data_order(self.ordering, self.columns)

        if len(self.sort) == 0:
            self.sort = None

        if self.additional_filters is not None:
            self.total = self.backend.count(
                self.request_values["retrieve"], self.additional_filters
            )
        else:
            self.total = self.backend.count(self.request_values["retrieve"])

        self.filtered = self.backend.datatables_data_filter(
            self.request_values, self.columns, self.additional_filters
        )

        self._fetch_results()

        if len(self.filtered) != 0:
            self.total_filtered = self.backend.count(
                self.request_values["retrieve"], self.filtered
            )
        else:
            self.total_filtered = self.total

    def _fetch_results(self):
        """
        Method responsible for querying the backend and fetching the results from the database
        """
        self.results = self.backend.query_docs(
            retrieve=self.request_values["retrieve"],
            dict_filter=self.filtered,
            query_filter=self.fields,
            sort=self.sort,
            limit=self.data_length.length,
            skip=self.data_length.start,
        )

    def __data_dimension(self):
        """
        Method responsible for retrieving the requested start and length parameters from the DataTables request values

        :return: Namedtuple 'data_length' with a start and length attribute
        :rtype: namedtuple
        """

        data_length = namedtuple("data_length", ["start", "length"])

        try:
            start = int(self.request_values.get("start", 0))
            length = int(self.request_values.get("length", 50))
        except (TypeError, ValueError):
            raise ValueError("Invalid pagination parameters")

        if start < 0:
            raise ValueError("Invalid pagination start parameter")

        # Config-driven limit (trusted operator setting)
        try:
            config_limit = int(Configuration.getPageLength())
        except Exception:
            config_limit = 50

        # Maximum page size aligned with DataTables UI configuration (default dropdown cap)
        MAX_PAGE_SIZE = max(config_limit, 100)

        if length < 1 or length > MAX_PAGE_SIZE:
            raise ValueError("Invalid pagination length parameter")

        result = data_length(start=start, length=length)

        return result
