from abc import ABC, abstractmethod
from datetime import datetime
import logging
import requests

from .models import Distribution, DistributionError
from .utils import datetime_to_epos_format

class Catalogue(ABC):
    '''The interface all catalogue-like objects are expected to implement'''
    @abstractmethod
    def search(self, query=None, **kwargs):
        pass


class EposCatalogue(Catalogue):
    '''this class encapsulates interactions with the catalogue of services
    check the Swagger documentation for advanced usage
    https://catalogue.staging.envri.eu/api/v1/ui
    '''
    base_address = 'https://catalogue.staging.envri.eu/api/v1'
    search_path = '/resources/search'
    details_path = '/resources/details/'
    
    def __init__(self, api_url = None):
        if api_url is None:
            self.api_url = self.base_address
        else:
            self.api_url = api_url
            
    def __parse_distributions(self, dists, facet = None):
        '''parses distribution JSON into a Distribution object'''
        for d in dists:
            try:
                yield Distribution.from_dict(d)
            except DistributionError as e:
                logging.error(e)


    def search(self, query:str='', startDate:datetime=None, endDate:datetime=None, bbox:tuple =None, keywords:list[str] =None,
               sciencedomains = None, servicetypes= None, organisations=None, exvs=None, facetstype = None,
               facets:bool = False):
        '''
        Allows to retrieve datasets as Distribution objects.
        You can query teh cataloge with a full text query and/or in a faceted way
        including time boundaries, geographical baoundaries, and other types of filter.

        Parameters
        ----------
        query:str
            A textual query in the likes of what you'd insert into a search engine
        startDate:datetime
            the beginning of the time interval of interest
        endDate: datetime
            the end of the time interval of interest
        bbox: tuple
            the four margins of the geographical area of interest.
            e.g. (10.70, 36.17, 28.98, 48.34)
        keywords: list[str]
            a list of keywords to filter research
        sciencedomains:

        servicetypes

        organisations

        exvs:str

        facestype: str
            one of the following values: 'categories', 'dataproviders', or 'serviceproviders'

        facets:bool
            whether or not to include facets in the search, which changes the structure of the
            output returned by the catalogue

        '''
        search_endpoint = self.api_url + self.search_path
        params = {
            'q': query, 
            'startDate': datetime_to_epos_format(startDate) if isinstance(startDate, datetime) else startDate,
            'endDate': datetime_to_epos_format(endDate) if isinstance(endDate, datetime) else endDate,
            'bbox': str(bbox)if bbox is not None else None,
            'keywords':str(keywords) if keywords is not None else None,
            'sciencedomains': sciencedomains,
            'servicetypes':servicetypes,
            'organisations':organisations,
            'exvs':str(exvs) if exvs is not None else None,
            'facetstype': facetstype if facetstype in ['categories', 'dataproviders', 'serviceproviders'] else None,
            'facets': facets
            
        }
        response_search = requests.get(search_endpoint, params=params)
        data = response_search.json()
        # parsing the JSON response to find distribution items
        if data.get('results') is not None:
            results = data.get('results')
            # if it's a faceted search, distributions are split into several
            # classes that are represented with a nested structure that looks like this
            # 'results' -> {'children':[
            #                    {'children':[
            #                          {'distributions':[...], 
            #                           'name':facet name},
            #                            ...
            #                                ]
            #                           },
            #                           ...
            #                       ]
            #              }
            if facets:
                if isinstance(results.get('children'), list):
                    for child in results.get('children'):
                        if isinstance(child.get('children'), list):
                            for nephew in child.get('children'):
                                if nephew.get('distributions') is not None:
                                     yield from self.__parse_distributions(nephew.get('distributions'), facet = nephew.get('name'))    
                                
            # else it's not a faceted search
            elif results.get('distributions') is not None:
                yield from self.__parse_distributions(results.get('distributions'))
        else:
            return None
                            


    def retrieve_resource(self, id):
        retrieve_endpoint = self.api_url + self.details_path
        response_retrieve = requests.get(retrieve_endpoint + '/' + str(id))
        return Distribution.from_dict(response_retrieve.json())
    
    