import requests
from abc import ABC, abstractmethod
from typing import BinaryIO, Generator, List

from superai.exceptions import SuperAIStorageError


class DataApiMixin(ABC):
    _resource = "data"

    @abstractmethod
    def request(self, uri, method, body_params=None, query_params=None, required_api_key=False):
        pass

    @property
    def resource(self):
        return self._resource

    def list_data(
        self,
        data_ids: List[str] = None,
        paths: List[str] = None,
        recursive: bool = False,
        signedUrl: bool = False,
        secondsTtl: int = 600,
        page: int = None,
        size: int = None,
    ) -> dict:
        """
        Get a paginated list of datasets, that can be filtered using an array of ids xor and array of paths
        :param data_ids: Array of data ids
        :param paths: Array of paths
        :param recursive: Get all datasets from recursive path (only takes first path of array)
        :param signedUrl: Get signed url for each dataset
        :param secondsTtl: Time to live for signed url
        :param page: Page number [0..N]
        :param size: Size of page
        :return: Paginated list of datasets dicts
        """
        query_params = {}
        if data_ids is not None:
            query_params["dataId"] = data_ids
        elif paths is not None:
            query_params["path"] = paths
            query_params["recursive"] = recursive
        query_params["signedUrl"] = signedUrl
        if signedUrl:
            query_params["secondsTtl"] = secondsTtl
        if page is not None:
            query_params["page"] = page
        if size is not None:
            query_params["size"] = size
        return self.request(self.resource, method="GET", query_params=query_params, required_api_key=True)

    def get_all_data(
        self,
        data_ids: List[str] = None,
        paths: List[str] = None,
        recursive: bool = False,
        signedUrl: bool = False,
        secondsTtl: int = 600,
    ) -> Generator[dict, None, None]:
        """
        Generator that retrieves all data filtered using an array of ids xor and array of paths
        :param data_ids: Array of data ids
        :param paths: Array of paths
        :param recursive: Get all datasets from recursive path (only takes first path of array)
        :param signedUrl: Get signed url for each dataset
        :param secondsTtl: Time to live for signed url
        :return: Generator that yields complete list of dicts with data objects
        """
        page = 0
        paginated_data = {"last": False}
        while not paginated_data["last"]:
            paginated_data = self.list_data(
                data_ids=data_ids,
                paths=paths,
                recursive=recursive,
                signedUrl=signedUrl,
                secondsTtl=secondsTtl,
                page=page,
                size=500,
            )
            for d in paginated_data["content"]:
                yield d
            page = page + 1

    def get_signed_url(self, path: str, secondsTtl: int = 600) -> dict:
        """
        Get signed url for a dataset given its path
        :param path: Dataset's path
        :param secondsTtl: Time to live for signed url
        :return: Dict with signedUrl of dataset
        """
        uri = f"{self.resource}/url"
        return self.request(
            uri, method="GET", query_params={"path": path, "secondsTtl": secondsTtl}, required_api_key=True
        )

    def delete_data(self, path: str) -> dict:
        """
        Delete dataset given its path
        :param path: Dataset's path
        :return: Dict with details of deleted dataset
        """
        return self.request(self.resource, method="DELETE", query_params={"path": path}, required_api_key=True)

    def upload_data(self, path: str, description: str, mimeType: str, file: BinaryIO) -> dict:
        """
        Create/update a dataset given its path using file and mimeType
        :param path: Path of dataset
        :param description: Description of dataset
        :param mimeType: Type of file
        :param file: Binary File value
        :return: Dataset created/updated
        """
        dataset = self.request(
            self.resource,
            method="POST",
            query_params={"path": path, "description": description, "mimeType": mimeType, "uploadUrl": True},
            required_api_key=True,
        )
        try:
            resp = requests.put(dataset.pop("uploadUrl"), data=file.read())
            if resp.status_code == 200 or resp.status_code == 201:
                return dataset
            else:
                raise SuperAIStorageError(
                    f'File {str(file)} referenced by dataset {dataset["path"]} couldn\'t be uploaded to super.AI '
                    f"Storage"
                )
        except Exception as e:
            raise SuperAIStorageError(
                f'File {str(file)} referenced by dataset {dataset["path"]} couldn\'t be uploaded to super.AI Storage '
                f"Error: {str(e)}"
            )
