<gh_stars>0

from copy import deepcopy

from pprint import PrettyPrinter
from semver import VersionInfo
from werkzeug.exceptions import BadRequest

from stac_compose.environment import SC_LIMIT_PER_PAGES, SC_LOGGING_LEVEL
from stac_compose.collections.business import CollectionsBusiness
from stac_compose.common import rename_fields_from_feature_collection, destructuring_dict, \
                                get_limit_to_search, add_context_field_in_the_feature_collection_if_it_does_not_exist
from stac_compose.logger import create_logger
from stac_compose.providers.business import ProvidersBusiness
from stac_compose.stac.services import StacServices


# create logger object
logger = create_logger(__name__, level=SC_LOGGING_LEVEL)

pp = PrettyPrinter(indent=4)


class StacBusiness():
    providers_business = ProvidersBusiness()

    @classmethod
    def get_stac_search(cls, provider_json, collection, bbox, datetime, query=None, page=1, limit=SC_LIMIT_PER_PAGES):
        """GET /stac/search"""

        logger.info('provider_json: %s', provider_json)
        logger.info('collection: %s', collection)
        logger.info('bbox: %s', bbox)
        logger.info('datetime: %s', datetime)
        logger.info('query: %s', query)
        logger.info('page: %s', page)
        logger.info('limit: %s', limit)
        logger.info('url: %s', provider_json['url'])

        parameters = []

        if isinstance(bbox, str):
            parameters.append('bbox={}'.format(bbox))
        elif isinstance(bbox, list):
            bbox = ",".join(list(map(str, bbox)))
            parameters.append('bbox={}'.format(bbox))
        else:
            raise BadRequest('`bbox` field is invalid: `{0}`, it should be a string or list, but its type is {1}.'.format(bbox, type(bbox)))

        parameters.append('collections={}'.format(collection))

        # if STAC <= '0.7.0'
        if datetime and (VersionInfo.parse(provider_json['version']) <= VersionInfo.parse('0.7.0')):
            parameters.append('time={}'.format(datetime))
        elif datetime:
            # default: STAC >= 0.9.0
            parameters.append('datetime={}'.format(datetime))

        # if cloud_cover:
        #     query += '&eo:cloud_cover=0/{}'.format(cloud_cover)

        parameters.append('page={}'.format(page))
        parameters.append('limit={}'.format(limit))

        parameters = "&".join(parameters)

        logger.info('parameters: %s', parameters)

        response = StacServices.get_stac_search(provider_json, parameters)

        # post processing to add field and rename other ones if it is necessary
        response = add_context_field_in_the_feature_collection_if_it_does_not_exist(response, page=page, limit=limit)
        response = rename_fields_from_feature_collection(response)

        # logger.info('response: %s', response)

        return response

    @classmethod
    def post_stac_search(cls, provider_json, collections, bbox, datetime=False, query=None, page=1, limit=SC_LIMIT_PER_PAGES):
        """POST /stac/search"""

        logger.info('provider_json: %s', provider_json)
        logger.info('collections: %s', collections)
        logger.info('type(collections): %s', type(collections))
        logger.info('bbox: %s', bbox)
        logger.info('datetime: %s', datetime)
        logger.info('query: %s', query)
        logger.info('page: %s', page)
        logger.info('limit: %s', limit)

        search_collection_as_property = provider_json['search_collection_as_property']

        logger.info('url: %s', provider_json['url'])
        logger.info('search_collection_as_property: %s\n', search_collection_as_property)

        data = {
            "bbox": bbox,
            "page": page,
            "limit": limit
        }

        if query is not None:
            data["query"] = query

        # if STAC <= '0.7.0'
        if datetime and (VersionInfo.parse(provider_json['version']) <= VersionInfo.parse('0.7.0')):
            data['time'] = datetime
        elif datetime:
            # default: STAC >= 0.9.0
            data['datetime'] = datetime

        # if `collections` is a list of collection, then I join all collections to send them
        if isinstance(collections, list):
            # `collections` is something like this `[{"name": "CBERS4_AWFI_L4_DN"}, ...]``
            # then the `map` function returns a list of collections (strings), for example: `['CBERS4_AWFI_L4_DN', ...]`
            # thus the `join` function joins the list of strings

            # data["collections"] = ','.join(list(map(lambda c: c['name'], collections)))
            data["collections"] = list(map(lambda c: c['name'], collections))

        # if `collections` is a string, in other words, is just one collection, then I just send it
        elif isinstance(collections, str):

            # if STAC supports just to search collection as property, then add it inside query
            if search_collection_as_property:
                # if `data` does not have `query` field, then initialize it
                if "query" not in data:
                    data["query"] = {}

                data["query"]["collection"] = {
                    "eq": collections
                }
            # else it searchs as STAC standard
            else:
                data["collections"] = [collections]

        else:
            raise BadRequest('`collections` must be instance of `list` or `str`.')

        logger.info('data: %s\n', data)

        response = StacServices.post_stac_search(provider_json, data)

        # an error has been raised
        if 'status_code' in response and 'description' in response:
            return response

        # logger.info(f'\n\n(1) response: {response}\n\n')

        # post processing to rename fields and add field is it is necessary
        response = add_context_field_in_the_feature_collection_if_it_does_not_exist(response, page=page, limit=limit)
        response = rename_fields_from_feature_collection(response)

        # logger.info(f'\n\n(2) response: {response}\n\n')

        return response

    @classmethod
    def stac_search(cls, method, provider_json, collections, bbox, datetime=False, query=None, page=1, limit=SC_LIMIT_PER_PAGES):
        logger.info('method: %s', method)
        logger.info('collections: %s\n', collections)

        if method == "GET":
            # return cls.get_stac_search(provider_json, collections, bbox, datetime, query=query, page=1, limit=limit)
            return CollectionsBusiness.stac_get_items(provider_json, collections, bbox, datetime=datetime, limit=limit)
        elif method == "POST":
            return cls.post_stac_search(provider_json, collections, bbox, datetime, query=query, page=1, limit=limit)
        else:
            raise BadRequest('Invalid method: {}'.format(method))

    @classmethod
    def stac_search_by_pagination(cls, result, method, provider_json, collection_name, bbox, datetime, query, limit, limit_to_search):
        # if there is more results to get, I'm going to search them by pagination
        for page in range(2, int(limit/SC_LIMIT_PER_PAGES) + 1):
            logger.info('page: %s', page)

            __result = cls.stac_search(method, provider_json, collection_name, bbox, datetime, query, page, limit_to_search)
            # logger.info('__result: %s', __result)

            # if I'm on other page, then I increase the old result
            result['features'] += __result['features']
            result['context']['returned'] += __result['context']['returned']

            # logger.info('__result: %s', __result)

        # get matched variable based on 'result['context']['matched']'
        context = result['context']
        matched = int(context['matched'])

        logger.info('context: %s', context)

        # if something was found, then fill 'limit' key with the true limit
        if matched:
            context['limit'] = limit

        return result

    @classmethod
    def post_search(cls, providers, bbox, datetime, limit=SC_LIMIT_PER_PAGES):
        logger.info('SC_LIMIT_PER_PAGES: %s\n', SC_LIMIT_PER_PAGES)

        logger.info('providers: %s', providers)
        logger.info('bbox: %s', bbox)
        logger.info('datetime: %s', datetime)
        logger.info('limit: %s\n', limit)

        result_dict = {}

        # check if there is a provider that does not exist before requesting features
        for provider in providers:
            provider_json = cls.providers_business.get_providers(provider['name'])
            if provider_json is None:
                raise BadRequest(f"`{provider['name']}` provider does not exist!")

        for provider in providers:
            logger.info(f'provider: {provider}')

            # destructuring dictionary contents into variables
            provider_name, method, collections, query = destructuring_dict(provider, 'name', 'method', 'collections', 'query')
            provider_json = cls.providers_business.get_providers(provider_name)

            logger.info('  provider_name: %s', provider_name)
            logger.info('  method: %s', method)
            logger.info('  collections: %s', collections)
            logger.info('  query: %s', query)
            logger.info('  provider_json: %s\n', provider_json)

            # if there is not a provider inside the dict, then initialize it
            if provider_name not in result_dict:
                result_dict[provider_name] = {}

            if provider_json['filter_mult_collection']:
                logger.warning('filter_mult_collection == True\n')

                limit_to_search = get_limit_to_search(limit)

                feature_collection = cls.stac_search(method, provider_json, collections, bbox, datetime, query, 1, limit_to_search)
                # logger.info('StacBusiness.post_search() - feature_collection: %s', feature_collection)

                # an error has been raised
                if 'status_code' in feature_collection and 'description' in feature_collection:
                    result_dict[provider_name] = feature_collection
                    continue

                logger.info('context: %s', feature_collection['context'])

                # remove `meta` key from dict
                metadata_related_to_collections = feature_collection['context'].pop('meta')

                logger.info('metadata_related_to_collections: %s', metadata_related_to_collections)

                # remove `features` key from dict
                features = feature_collection.pop('features')

                logger.info('StacBusiness.post_search() - collections:')

                for collection in collections:
                    collection_name = collection['name']
                    logger.info('StacBusiness.post_searc() - collection_name: %s', collection_name)

                    result = list(filter(lambda f: f['collection'] == collection_name, features))
                    metadata = list(filter(lambda f: f['name'] == collection_name, metadata_related_to_collections))

                    logger.info('metadata: %s', metadata)

                    fc_structure = deepcopy(feature_collection)
                    fc_structure['features'] = result
                    fc_structure['context']['matched'] = metadata[0]['context']['matched']
                    fc_structure['context']['returned'] = metadata[0]['context']['returned']

                    # add the found collection to the result
                    result_dict[provider_name][collection_name] = fc_structure

            else:
                logger.warning('filter_mult_collection == False\n')
                logger.info('collections:')

                for collection in collections:
                    collection_name = collection['name']
                    logger.info('collection_name: %s', collection_name)

                    limit_to_search = get_limit_to_search(limit)

                    # first: I'm searching by the first page
                    result = cls.stac_search(method, provider_json, collection_name, bbox, datetime, query, 1, limit_to_search)
                    # logger.info('StacBusiness.post_search() - result: %s', result)

                    # logger.info('context: %s', result['context'])

                    # if I've already got all features, then I go out of the loop
                    if limit <= SC_LIMIT_PER_PAGES or result['context']['matched'] <= SC_LIMIT_PER_PAGES:
                        logger.info('just one request was did')
                    # if there is more features to get, then I search by them
                    else:
                        logger.info('more requests are required')

                        result = cls.stac_search_by_pagination(
                            result, method, provider_json, collection_name, bbox, datetime,
                            query, limit, limit_to_search
                        )

                    # add the found collection to the result
                    result_dict[provider_name][collection_name] = result

        # logger.debug('StacBusiness.post_search() - result_dict: %s', result_dict)

        return result_dict

from copy import deepcopy

from pprint import PrettyPrinter
from semver import VersionInfo
from werkzeug.exceptions import BadRequest

from stac_compose.environment import SC_LIMIT_PER_PAGES, SC_LOGGING_LEVEL
from stac_compose.collections.business import CollectionsBusiness
from stac_compose.common import rename_fields_from_feature_collection, destructuring_dict, \
                                get_limit_to_search, add_context_field_in_the_feature_collection_if_it_does_not_exist
from stac_compose.logger import create_logger
from stac_compose.providers.business import ProvidersBusiness
from stac_compose.stac.services import StacServices


# create logger object
logger = create_logger(__name__, level=SC_LOGGING_LEVEL)

pp = PrettyPrinter(indent=4)


class StacBusiness():
    providers_business = ProvidersBusiness()

    @classmethod
    def get_stac_search(cls, provider_json, collection, bbox, datetime, query=None, page=1, limit=SC_LIMIT_PER_PAGES):
        """GET /stac/search"""

        logger.info('provider_json: %s', provider_json)
        logger.info('collection: %s', collection)
        logger.info('bbox: %s', bbox)
        logger.info('datetime: %s', datetime)
        logger.info('query: %s', query)
        logger.info('page: %s', page)
        logger.info('limit: %s', limit)
        logger.info('url: %s', provider_json['url'])

        parameters = []

        if isinstance(bbox, str):
            parameters.append('bbox={}'.format(bbox))
        elif isinstance(bbox, list):
            bbox = ",".join(list(map(str, bbox)))
            parameters.append('bbox={}'.format(bbox))
        else:
            raise BadRequest('`bbox` field is invalid: `{0}`, it should be a string or list, but its type is {1}.'.format(bbox, type(bbox)))

        parameters.append('collections={}'.format(collection))

        # if STAC <= '0.7.0'
        if datetime and (VersionInfo.parse(provider_json['version']) <= VersionInfo.parse('0.7.0')):
            parameters.append('time={}'.format(datetime))
        elif datetime:
            # default: STAC >= 0.9.0
            parameters.append('datetime={}'.format(datetime))

        # if cloud_cover:
        #     query += '&eo:cloud_cover=0/{}'.format(cloud_cover)

        parameters.append('page={}'.format(page))
        parameters.append('limit={}'.format(limit))

        parameters = "&".join(parameters)

        logger.info('parameters: %s', parameters)

        response = StacServices.get_stac_search(provider_json, parameters)

        # post processing to add field and rename other ones if it is necessary
        response = add_context_field_in_the_feature_collection_if_it_does_not_exist(response, page=page, limit=limit)
        response = rename_fields_from_feature_collection(response)

        # logger.info('response: %s', response)

        return response

    @classmethod
    def post_stac_search(cls, provider_json, collections, bbox, datetime=False, query=None, page=1, limit=SC_LIMIT_PER_PAGES):
        """POST /stac/search"""

        logger.info('provider_json: %s', provider_json)
        logger.info('collections: %s', collections)
        logger.info('type(collections): %s', type(collections))
        logger.info('bbox: %s', bbox)
        logger.info('datetime: %s', datetime)
        logger.info('query: %s', query)
        logger.info('page: %s', page)
        logger.info('limit: %s', limit)

        search_collection_as_property = provider_json['search_collection_as_property']

        logger.info('url: %s', provider_json['url'])
        logger.info('search_collection_as_property: %s\n', search_collection_as_property)

        data = {
            "bbox": bbox,
            "page": page,
            "limit": limit
        }

        if query is not None:
            data["query"] = query

        # if STAC <= '0.7.0'
        if datetime and (VersionInfo.parse(provider_json['version']) <= VersionInfo.parse('0.7.0')):
            data['time'] = datetime
        elif datetime:
            # default: STAC >= 0.9.0
            data['datetime'] = datetime

        # if `collections` is a list of collection, then I join all collections to send them
        if isinstance(collections, list):
            # `collections` is something like this `[{"name": "CBERS4_AWFI_L4_DN"}, ...]``
            # then the `map` function returns a list of collections (strings), for example: `['CBERS4_AWFI_L4_DN', ...]`
            # thus the `join` function joins the list of strings

            # data["collections"] = ','.join(list(map(lambda c: c['name'], collections)))
            data["collections"] = list(map(lambda c: c['name'], collections))

        # if `collections` is a string, in other words, is just one collection, then I just send it
        elif isinstance(collections, str):

            # if STAC supports just to search collection as property, then add it inside query
            if search_collection_as_property:
                # if `data` does not have `query` field, then initialize it
                if "query" not in data:
                    data["query"] = {}

                data["query"]["collection"] = {
                    "eq": collections
                }
            # else it searchs as STAC standard
            else:
                data["collections"] = [collections]

        else:
            raise BadRequest('`collections` must be instance of `list` or `str`.')

        logger.info('data: %s\n', data)

        response = StacServices.post_stac_search(provider_json, data)

        # an error has been raised
        if 'status_code' in response and 'description' in response:
            return response

        # logger.info(f'\n\n(1) response: {response}\n\n')

        # post processing to rename fields and add field is it is necessary
        response = add_context_field_in_the_feature_collection_if_it_does_not_exist(response, page=page, limit=limit)
        response = rename_fields_from_feature_collection(response)

        # logger.info(f'\n\n(2) response: {response}\n\n')

        return response

    @classmethod
    def stac_search(cls, method, provider_json, collections, bbox, datetime=False, query=None, page=1, limit=SC_LIMIT_PER_PAGES):
        logger.info('method: %s', method)
        logger.info('collections: %s\n', collections)

        if method == "GET":
            # return cls.get_stac_search(provider_json, collections, bbox, datetime, query=query, page=1, limit=limit)
            return CollectionsBusiness.stac_get_items(provider_json, collections, bbox, datetime=datetime, limit=limit)
        elif method == "POST":
            return cls.post_stac_search(provider_json, collections, bbox, datetime, query=query, page=1, limit=limit)
        else:
            raise BadRequest('Invalid method: {}'.format(method))

    @classmethod
    def stac_search_by_pagination(cls, result, method, provider_json, collection_name, bbox, datetime, query, limit, limit_to_search):
        # if there is more results to get, I'm going to search them by pagination
        for page in range(2, int(limit/SC_LIMIT_PER_PAGES) + 1):
            logger.info('page: %s', page)

            __result = cls.stac_search(method, provider_json, collection_name, bbox, datetime, query, page, limit_to_search)
            # logger.info('__result: %s', __result)

            # if I'm on other page, then I increase the old result
            result['features'] += __result['features']
            result['context']['returned'] += __result['context']['returned']

            # logger.info('__result: %s', __result)

        # get matched variable based on 'result['context']['matched']'
        context = result['context']
        matched = int(context['matched'])

        logger.info('context: %s', context)

        # if something was found, then fill 'limit' key with the true limit
        if matched:
            context['limit'] = limit

        return result

    @classmethod
    def post_search(cls, providers, bbox, datetime, limit=SC_LIMIT_PER_PAGES):
        logger.info('SC_LIMIT_PER_PAGES: %s\n', SC_LIMIT_PER_PAGES)

        logger.info('providers: %s', providers)
        logger.info('bbox: %s', bbox)
        logger.info('datetime: %s', datetime)
        logger.info('limit: %s\n', limit)

        result_dict = {}

        # check if there is a provider that does not exist before requesting features
        for provider in providers:
            provider_json = cls.providers_business.get_providers(provider['name'])
            if provider_json is None:
                raise BadRequest(f"`{provider['name']}` provider does not exist!")

        for provider in providers:
            logger.info(f'provider: {provider}')

            # destructuring dictionary contents into variables
            provider_name, method, collections, query = destructuring_dict(provider, 'name', 'method', 'collections', 'query')
            provider_json = cls.providers_business.get_providers(provider_name)

            logger.info('  provider_name: %s', provider_name)
            logger.info('  method: %s', method)
            logger.info('  collections: %s', collections)
            logger.info('  query: %s', query)
            logger.info('  provider_json: %s\n', provider_json)

            # if there is not a provider inside the dict, then initialize it
            if provider_name not in result_dict:
                result_dict[provider_name] = {}

            if provider_json['filter_mult_collection']:
                logger.warning('filter_mult_collection == True\n')

                limit_to_search = get_limit_to_search(limit)

                feature_collection = cls.stac_search(method, provider_json, collections, bbox, datetime, query, 1, limit_to_search)
                # logger.info('StacBusiness.post_search() - feature_collection: %s', feature_collection)

                # an error has been raised
                if 'status_code' in feature_collection and 'description' in feature_collection:
                    result_dict[provider_name] = feature_collection
                    continue

                logger.info('context: %s', feature_collection['context'])

                # remove `meta` key from dict
                metadata_related_to_collections = feature_collection['context'].pop('meta')

                logger.info('metadata_related_to_collections: %s', metadata_related_to_collections)

                # remove `features` key from dict
                features = feature_collection.pop('features')

                logger.info('StacBusiness.post_search() - collections:')

                for collection in collections:
                    collection_name = collection['name']
                    logger.info('StacBusiness.post_searc() - collection_name: %s', collection_name)

                    result = list(filter(lambda f: f['collection'] == collection_name, features))
                    metadata = list(filter(lambda f: f['name'] == collection_name, metadata_related_to_collections))

                    logger.info('metadata: %s', metadata)

                    fc_structure = deepcopy(feature_collection)
                    fc_structure['features'] = result
                    fc_structure['context']['matched'] = metadata[0]['context']['matched']
                    fc_structure['context']['returned'] = metadata[0]['context']['returned']

                    # add the found collection to the result
                    result_dict[provider_name][collection_name] = fc_structure

            else:
                logger.warning('filter_mult_collection == False\n')
                logger.info('collections:')

                for collection in collections:
                    collection_name = collection['name']
                    logger.info('collection_name: %s', collection_name)

                    limit_to_search = get_limit_to_search(limit)

                    # first: I'm searching by the first page
                    result = cls.stac_search(method, provider_json, collection_name, bbox, datetime, query, 1, limit_to_search)
                    # logger.info('StacBusiness.post_search() - result: %s', result)

                    # logger.info('context: %s', result['context'])

                    # if I've already got all features, then I go out of the loop
                    if limit <= SC_LIMIT_PER_PAGES or result['context']['matched'] <= SC_LIMIT_PER_PAGES:
                        logger.info('just one request was did')
                    # if there is more features to get, then I search by them
                    else:
                        logger.info('more requests are required')

                        result = cls.stac_search_by_pagination(
                            result, method, provider_json, collection_name, bbox, datetime,
                            query, limit, limit_to_search
                        )

                    # add the found collection to the result
                    result_dict[provider_name][collection_name] = result

        # logger.debug('StacBusiness.post_search() - result_dict: %s', result_dict)

        return result_dict
# create logger object GET /stac/search # if STAC <= '0.7.0' # default: STAC >= 0.9.0 # if cloud_cover: #     query += '&eo:cloud_cover=0/{}'.format(cloud_cover) # post processing to add field and rename other ones if it is necessary # logger.info('response: %s', response) POST /stac/search # if STAC <= '0.7.0' # default: STAC >= 0.9.0 # if `collections` is a list of collection, then I join all collections to send them # `collections` is something like this `[{"name": "CBERS4_AWFI_L4_DN"}, ...]`` # then the `map` function returns a list of collections (strings), for example: `['CBERS4_AWFI_L4_DN', ...]` # thus the `join` function joins the list of strings # data["collections"] = ','.join(list(map(lambda c: c['name'], collections))) # if `collections` is a string, in other words, is just one collection, then I just send it # if STAC supports just to search collection as property, then add it inside query # if `data` does not have `query` field, then initialize it # else it searchs as STAC standard # an error has been raised # logger.info(f'\n\n(1) response: {response}\n\n') # post processing to rename fields and add field is it is necessary # logger.info(f'\n\n(2) response: {response}\n\n') # return cls.get_stac_search(provider_json, collections, bbox, datetime, query=query, page=1, limit=limit) # if there is more results to get, I'm going to search them by pagination # logger.info('__result: %s', __result) # if I'm on other page, then I increase the old result # logger.info('__result: %s', __result) # get matched variable based on 'result['context']['matched']' # if something was found, then fill 'limit' key with the true limit # check if there is a provider that does not exist before requesting features # destructuring dictionary contents into variables # if there is not a provider inside the dict, then initialize it # logger.info('StacBusiness.post_search() - feature_collection: %s', feature_collection) # an error has been raised # remove `meta` key from dict # remove `features` key from dict # add the found collection to the result # first: I'm searching by the first page # logger.info('StacBusiness.post_search() - result: %s', result) # logger.info('context: %s', result['context']) # if I've already got all features, then I go out of the loop # if there is more features to get, then I search by them # add the found collection to the result # logger.debug('StacBusiness.post_search() - result_dict: %s', result_dict)