from __future__ import annotations
import json

from typing import TYPE_CHECKING, Generic, TypeVar

from pyhuntress.utils.helpers import parse_link_headers, parse_response_body

if TYPE_CHECKING:
    from collections.abc import Iterable

    from pydantic import BaseModel
    from requests import Response

    from pyhuntress.types import RequestParams


TModel = TypeVar("TModel", bound="BaseModel")

if TYPE_CHECKING:
    from pyhuntress.interfaces import IPaginateable


class PaginatedResponse(Generic[TModel]):
    """
    PaginatedResponse is a wrapper class for handling paginated responses from the
    Huntress API. It provides methods for navigating through the pages of the response
    and accessing the data contained within each page.

    The class is designed to work with HuntressEndpoint and its derived classes to
    parse the API response into model instances. It also supports iteration, allowing
    the user to loop through the items within the paginated response.

    PaginatedResponse uses a generic type variable TModel, which represents the
    expected model type for the response data. This allows for type-safe handling
    of model instances throughout the class.
    """

    def __init__(
        self,
        response: Response,
        response_model: type[TModel],
        endpointmodel: IPaginateable,
        endpoint: str,
        page: int,
        limit: int,
        params: RequestParams | None = None,
    ) -> None:
        """
        PaginatedResponse is a wrapper class for handling paginated responses from the
        Huntress API. It provides methods for navigating through the pages of the response
        and accessing the data contained within each page.

        The class is designed to work with HuntressEndpoint and its derived classes to
        parse the API response into model instances. It also supports iteration, allowing
        the user to loop through the items within the paginated response.

        PaginatedResponse uses a generic type variable TModel, which represents the
        expected model type for the response data. This allows for type-safe handling
        of model instances throughout the class.
        """
        self._initialize(response, response_model, endpointmodel, endpoint, page, limit, params)

    def _initialize(
        self,
        response: Response,
        response_model: type[TModel],
        endpointmodel: IPaginateable,
        endpoint: str,
        page: int,
        limit: int,
        params: RequestParams | None = None,
    ):
        """
        Initialize the instance variables using the provided response, endpointmodel, and page size.

        Args:
            response: The raw response object from the API.
            endpointmodel (HuntressEndpoint[TModel]): The endpointmodel associated with the response.
            endpoint: The endpoint url to extract the data
            limit (int): The number of items per page.
        """
        self.response = response
        self.response_model = response_model
        self.endpointmodel = endpointmodel
        self.endpoint = endpoint
        self.limit = limit
        # Get page data from the response body
        try:
            self.parsed_pagination_response = parse_response_body(json.loads(response.content.decode('utf-8')).get('pagination', {}))
        except:
            self.parsed_pagination_response = parse_response_body(json.loads(response.content.decode('utf-8')).get('meta.page', {}))
        self.params = params
        if self.parsed_pagination_response is not None:
            # Huntress SIEM API gives us a handy response to parse for Pagination
            self.has_next_page: bool = self.parsed_pagination_response.get("has_next_page", False)
            self.has_prev_page: bool = self.parsed_pagination_response.get("has_prev_page", False)
            self.first_page: int = self.parsed_pagination_response.get("first_page", None)
            self.prev_page: int = self.parsed_pagination_response.get("prev_page", None)
            self.next_page: int = self.parsed_pagination_response.get("next_page", None)
            self.last_page: int = self.parsed_pagination_response.get("last_page", None)
        else:
            # Huntress Managed SAT might, haven't worked on this yet
            self.has_next_page: bool = True
            self.has_prev_page: bool = page > 1
            self.first_page: int = 1
            self.prev_page = page - 1 if page > 1 else 1
            self.next_page = page + 1
            self.last_page = 999999
        self.data: list[TModel] = [response_model.model_validate(d) for d in response.json().get(endpoint, {})]
        self.has_data = self.data and len(self.data) > 0
        self.index = 0

    def get_next_page(self) -> PaginatedResponse[TModel]:
        """
        Fetch the next page of the paginated response.

        Returns:
            PaginatedResponse[TModel]: The updated PaginatedResponse instance
            with the data from the next page or None if there is no next page.
        """
        if not self.has_next_page or not self.next_page:
            self.has_data = False
            return self

        next_response = self.endpointmodel.paginated(self.next_page, self.limit, self.params)
        self._initialize(
            next_response.response,
            next_response.response_model,
            next_response.endpointmodel,
            next_response.endpoint,
            self.next_page,
            next_response.limit,
            self.params,
        )
        return self

    def get_previous_page(self) -> PaginatedResponse[TModel]:
        """
        Fetch the next page of the paginated response.

        Returns:
            PaginatedResponse[TModel]: The updated PaginatedResponse instance
            with the data from the next page or None if there is no next page.
        """
        if not self.has_prev_page or not self.prev_page:
            self.has_data = False
            return self

        prev_response = self.endpointmodel.paginated(self.prev_page, self.limit, self.params)
        self._initialize(
            prev_response.response,
            prev_response.response_model,
            prev_response.endpointmodel,
            self.prev_page,
            prev_response.limit,
            self.params,
        )
        return self

    def all(self) -> Iterable[TModel]:
        """
        Iterate through all items in the paginated response, across all pages.

        Yields:
            TModel: An instance of the model class for each item in the paginated response.
        """
        while self.has_data:
            yield from self.data
            self.get_next_page()

    def __iter__(self):
        """
        Implement the iterator protocol for the PaginatedResponse class.

        Returns:
            PaginatedResponse[TModel]: The current instance of the PaginatedResponse.
        """
        return self

    def __dict__(self):
        """
        Implement the iterator protocol for the PaginatedResponse class.

        Returns:
            PaginatedResponse[TModel]: The current instance of the PaginatedResponse.
        """
        return self.data

    def __next__(self):
        """
        Implement the iterator protocol by getting the next item in the data.

        Returns:
            TModel: The next item in the data.

        Raises:
            StopIteration: If there are no more items in the data.
        """
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration
