"""Wherein is contained the Abstract Base Class for Index."""

import abc
import warnings
from datetime import datetime
from typing import Optional

import pandas as pd
from fsspec import AbstractFileSystem


class IndexABC(abc.ABC):
    """Abstract Base Class for the Index."""

    @abc.abstractmethod
    def __init__(
        self, file_system: AbstractFileSystem, pantry_path: str, **kwargs
    ):
        """Initializes the Index class.

        Parameters
        ----------
        file_system: fsspec object
            The fsspec object which hosts the pantry we desire to index.
        pantry_path: str
            Path to the pantry root which we want to index.
        **setup_config: dict (required)
            Existing metadata for the Index.
        Optional kwargs controlled by concrete implementations.
        """

        self._file_system = file_system
        self._pantry_path = pantry_path
        self.setup_config = kwargs.get("setup_config", {})
        self.generate_config()

    @property
    @abc.abstractmethod
    def file_system(self) -> AbstractFileSystem:
        """The file system of the pantry referenced by this Index."""

    @property
    @abc.abstractmethod
    def pantry_path(self) -> str:
        """The pantry path referenced by this Index."""

    @abc.abstractmethod
    def generate_metadata(self, **kwargs) -> dict:
        """(Deprecated) Generate the metadata for the index."""
        warnings.warn(
            UserWarning("This function is being deprecated in a future weave "
                        "release. Please use generate_config() instead.")
        )
        return self.generate_config(**kwargs)

    @abc.abstractmethod
    def generate_config(self, **kwargs) -> dict:
        """Generate the setup config for the index.

        Parameters
        ----------
        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        A dictionary of metadata for the index.
        """
        self.setup_config["index_type"] = str(self)

    @abc.abstractmethod
    def generate_index(self, **kwargs):
        """Populates the index from the file system.

        Generate the index by scraping the pantry and adding the manifest data
        of found baskets to the index.

        Parameters
        ----------
        Optional kwargs controlled by concrete implementations.
        """

    @abc.abstractmethod
    def clear_index(self, refresh: bool = False, **kwargs):
        """Deletes/clears the previously populated index.

        Delete the index entirely, optionally regenerating a new one
        if the refresh flag is set to True.

        Parameters
        ----------
        refresh: bool (default=False)
            Regenerates the index after clearing the old one.
        Optional kwargs controlled by concrete implementations.
        """

    @abc.abstractmethod
    def to_pandas_df(
        self, max_rows: Optional[int] = None, offset: int = 0, **kwargs
    ) -> pd.DataFrame:
        """Returns the pandas dataframe representation of the index.

        Parameters
        ----------
        max_rows: int or None (default=None)
            Max rows returned in the pandas dataframe. If None, all rows will
            be returned.
        offset: int (default=0)
            Offset from the beginning of the index to begin the query

        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        Returns a pandas dataframe of the manifest data of the baskets in the
        pantry.
        """

    @abc.abstractmethod
    def track_basket(self, entry_df: pd.DataFrame, **kwargs):
        """Track a basket (or many baskets) from the pantry with the Index.

        Parameters
        ----------
        entry_df : pandas.DataFrame
            Uploaded baskets to append to the index.
        Optional kwargs controlled by concrete implementations.
        """

    @abc.abstractmethod
    def untrack_basket(self, basket_address: str | list[str], **kwargs):
        """Remove a basket from being tracked of given UUID or path.

        Parameters
        ----------
        basket_address: str or [str]
            Argument can take one of two forms: either a path to the basket
            directory, or the UUID of the basket. These may also be passed in
            as a list.
        Optional kwargs controlled by concrete implementations.
        """

    @abc.abstractmethod
    def get_rows(
        self, basket_address: str | list[str], **kwargs
    ) -> pd.DataFrame:
        """Returns a pd.DataFrame row information of given UUID or path.

        Parameters
        ----------
        basket_address: str or [str]
            Argument can take one of two forms: either a path to the basket
            directory, or the UUID of the basket. These may also be passed in
            as a list.
        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame of the Manifest information for the requested
        basket(s).
        """

    @abc.abstractmethod
    def get_parents(self, basket_address: str, **kwargs) -> pd.DataFrame:
        """Returns a pandas dataframe of all parents of a basket.

        Parameters
        ----------
        basket_address: str
            Argument can take one of two forms: either a path to the basket
            directory, or the UUID of the basket.
        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame containing all the manifest data AND generation level
        of parents (and recursively their parents) of the given basket.
        """

    @abc.abstractmethod
    def get_children(self, basket_address: str, **kwargs) -> pd.DataFrame:
        """Returns a pandas dataframe of all children of a basket.

        Parameters
        ----------
        basket_address: str
            Argument can take one of two forms: either a path to the basket
            directory, or the UUID of the basket.
        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame containing all the manifest data AND generation level
        of children (and recursively their children) of the given basket.
        """

    @abc.abstractmethod
    def get_baskets_of_type(
        self,
        basket_type: str,
        max_rows: Optional[int] = None,
        offset: int = 0,
        **kwargs,
    ) -> pd.DataFrame:
        """Returns a pandas dataframe containing baskets of basket_type.

        Parameters
        ----------
        basket_type: str
            The basket type to filter for.
        max_rows: int or None (default=None)
            Max rows returned in the pandas dataframe. If None, all rows will
            be returned.
        offset: int (default=0)
            Offset from the beginning of the index to begin the query

        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame containing the manifest data of baskets of the type.
        """

    @abc.abstractmethod
    def get_baskets_of_label(
        self,
        basket_label: str,
        max_rows: Optional[int] = None,
        offset: int = 0,
        **kwargs,
    ) -> pd.DataFrame:
        """Returns a pandas dataframe containing baskets with label.

        Parameters
        ----------
        basket_label: str
            The label to filter for.
        max_rows: int or None (default=None)
            Max rows returned in the pandas dataframe. If None, all rows will
            be returned.
        offset: int (default=0)
            Offset from the beginning of the index to begin the query

        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame containing the manifest data of baskets with the
        label.
        """

    @abc.abstractmethod
    def get_baskets_by_upload_time(
        self,
        start_time: Optional[datetime] = None,
        end_time: Optional[datetime] = None,
        max_rows: Optional[int] = None,
        offset: int = 0,
        **kwargs,
    ) -> pd.DataFrame:
        """Returns a pandas dataframe of baskets uploaded between two times.

        Parameters
        ----------
        start_time: datetime.datetime (default=None)
            The start datetime object to filter between. If None, will filter
            from the beginning of time.
        end_time: datetime.datetime (default=None)
            The end datetime object to filter between. If None, will filter
            to the current datetime.
        max_rows: int or None (default=None)
            Max rows returned in the pandas dataframe. If None, all rows will
            be returned.
        offset: int (default=0)
            Offset from the beginning of the index to begin the query.

        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame containing the manifest data of baskets uploaded
        between the start and end times.
        """

        if start_time is not None and not isinstance(start_time,datetime):
            raise ValueError("start_time is not datetime object.")
        if end_time is not None and not isinstance(end_time,datetime):
            raise ValueError("end_time is not datetime object.")

    @abc.abstractmethod
    def query(self, expr: str, **kwargs) -> pd.DataFrame:
        """Returns a pandas dataframe of the results of the query.

        Parameters
        ----------
        expr: str
            An expression passed to the backend. An example could be a SQL or
            pandas query. Largely dependent on concrete implementations.
        Optional kwargs controlled by concrete implementations.

        Returns
        ----------
        pandas.DataFrame of the resulting query.
        """

    @abc.abstractmethod
    def __len__(self) -> int:
        """Returns the number of baskets in the index."""

    @abc.abstractmethod
    def __str__(self) -> str:
        """Returns the str instantiation type of this Index (ie 'IndexSQL')."""
