# Copyright (C) 2020-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""
This module contains a :class:`DataIndexApi` class to perform API operations.

The HERE API reference documentation used in this module can be found here:
|index_api_reference|

.. |index_api_reference| raw:: html

   <a href="https://developer.here.com/documentation/data-api/api-reference-index.html" target="_blank">Index API Reference</a>  # noqa E501
"""

from typing import Optional

from here.platform.api.base_api import BaseApi
from here.platform.auth import Auth
from here.platform.config import ApplicationConfig, PlatformConfig
from here.platform.exceptions import PlatformException


class DataIndexApi(BaseApi):
    """
    This class provides access to HERE platform Data Index APIs.

    Use the index service to get the data handles of the partitions that match a query. Then, use
    the data handles with the blob service to get the data from the partitions. You can also use
    the index service to publish data to an index layer.
    """

    def __init__(
        self,
        base_url: str,
        platform_config: PlatformConfig,
        application_config: ApplicationConfig,
        auth: Optional[Auth],
        proxies: Optional[dict] = None,
    ):
        """
        Instantiate DataIndexApi object.

        :param base_url: base url
        :param platform_config: a mandatory :class:`PlatformConfig` object to provide
        configuration information for the API.
        :param application_config: a mandatory :class:`ApplicationConfig` object to provide
        configuration information for the API.
        :param auth: an Authentication instance.
        :param proxies: an optional proxy configuration. Defaults to the environment proxy
        configuration.
        """
        super(DataIndexApi, self).__init__(
            platform_config=platform_config,
            application_config=application_config,
            auth=auth,
            proxies=proxies,
        )
        self.base_url = base_url

    def perform_query(
        self,
        layer_id: str,
        query: str,
        part: Optional[str] = None,
        billing_tag: Optional[str] = None,
    ) -> dict:
        """
        Query the index layer for the partitions that match the query. Returns each partition
        that matches the query, including each partition's data handle, which you use with the
        blob API to retrieve data for each partition.

        :param layer_id: The ID of the index layer you want to query.
        :param query: An RSQL query to use to retrieve partitions that match the query. For more
            information, see Get Index Data from an Index Layer. The query must use the indexing
            attributes defined in the index layer.
        :param part: Indicates which part of the layer shall be queried.
        :param billing_tag: A string which is used for grouping billing records.
        :return: response from the API.
        :raises PlatformException: If platform responds with an HTTP error.
        """
        path = f"/layers/{layer_id}"
        url = self.format_url(self.base_url, path)
        params = {"query": query, "part": part, "billingTag": billing_tag}
        resp = self.get(url, params=params)
        if resp.status_code == 200:
            resp_json: dict = resp.json()
            return resp_json
        else:
            raise PlatformException(resp)

    def perform_update(self, layer_id: str, body: dict, billing_tag: Optional[str] = None):
        """
        Modify partitions in an index layer.

        :param layer_id: The layer ID of the index layer.
        :param body: Contains the data you want to add or remove from the index layer
        :param billing_tag: A string which is used for grouping billing records.
        :raises PlatformException: If platform responds with an HTTP error.
        """
        path = f"/layers/{layer_id}"
        url = self.format_url(self.base_url, path)
        params = {"billingTag": billing_tag}
        resp = self.put(url, data=body, params=params)
        if resp.status_code == 200:
            return
        else:
            raise PlatformException(resp)

    def insert_indexes(self, layer_id: str, body: list, billing_tag: Optional[str] = None):
        """
        Add index data for a given data blob to an index layer.

        :param body: An array of index attributes and values to be inserted.
        :param layer_id: The layer ID of the index layer.
        :param billing_tag: A string which is used for grouping billing records.
        :raises PlatformException: If platform responds with an HTTP error.
        """
        path = f"/layers/{layer_id}"
        url = self.format_url(self.base_url, path)
        params = {"billingTag": billing_tag}
        resp = self.post(url, data=body, params=params)
        if resp.status_code in [200, 201]:
            # Note: 200 is returned only by LDS
            return
        else:
            raise PlatformException(resp)

    def perform_delete(
        self, layer_id: str, delete_query: str, billing_tag: Optional[str] = None
    ) -> dict:
        """
        Cancel a publication if it has not yet been submitted.

        :param layer_id: The ID of the index layer you want to delete from.
        :param delete_query: An RSQL query to use to delete the partitions that match the query.
            The query must use the indexing attributes defined in the index layer.
        :param billing_tag: A string which is used for grouping billing records.
        :return: response from the API.
        :raises PlatformException: If platform responds with an HTTP error.
        """
        path = f"/layers/{layer_id}"
        url = self.format_url(self.base_url, path)
        params = {"deleteQuery": delete_query, "billingTag": billing_tag}
        resp = self.delete(url, params)
        if resp.status_code == 202:
            resp_json: dict = resp.json()
            return resp_json
        else:
            raise PlatformException(resp)

    def get_delete_request_status(
        self,
        layer_id: str,
        delete_id: str,
        billing_tag: Optional[str] = None,
    ) -> dict:
        """
        Return the details of the specified delete request. Delete request can be in one of the
        following states: Scheduled, Processing, Failed, Succeeded.

        :param layer_id: The ID of the index layer you want to get the delete request status for.
        :param delete_id: Id of the delete request returned from the index delete operation.
        :param billing_tag: A string which is used for grouping billing records.
        :return: response from the API.
        :raises PlatformException: If platform responds with an HTTP error.
        """
        path = f"/layers/{layer_id}/deleteRequest/{delete_id}"
        url = self.format_url(self.base_url, path)
        params = {"billingTag": billing_tag}
        resp = self.get(url, params=params)
        if resp.status_code == 200:
            resp_json: dict = resp.json()
            return resp_json
        else:
            raise PlatformException(resp)

    def get_parts(
        self, layer_id: str, num_requested_parts: int, billing_tag: Optional[str] = None
    ) -> dict:
        """
        Return a list of Part Ids which represent the layer parts that can be used to limit the
        scope of a query operation. This allows to run parallel queries with multiple parts. The
        user has to provide the desired number of parts and the service will return a list of Part
        Ids. Please note in some cases the requested number of parts will make them too small and
        in this case the service might return lesser amount of the parts than requested.

        :param layer_id: The ID of the index layer you want to query.
        :param num_requested_parts: Indicates requested number of layer parts.
        :param billing_tag: A string which is used for grouping billing records.
        :return: response from the API.
        :raises PlatformException: If platform responds with an HTTP error.
        """
        path = f"/layers/{layer_id}/parts"
        url = self.format_url(self.base_url, path)
        params = {"numRequestedParts": num_requested_parts, "billingTag": billing_tag}
        resp = self.get(url, params=params)
        if resp.status_code == 200:
            resp_json: dict = resp.json()
            return resp_json
        else:
            raise PlatformException(resp)
