import pandas as pd
import requests

from .data_loader import Data_Loader, str2json, _url_error_msg, _process_date
from .csv_class import TqdmReader
from ..datetime_parser import to_datetime
from ..exceptions import OPD_DataUnavailableError, OPD_TooManyRequestsError
from .. import log

logger = log.get_logger()

class Opendatasoft(Data_Loader):
    """
    A class for accessing data from Opendatasoft clients

    Parameters
    ----------
    url : str
        URL
    data_set : str
        Dataset ID
    date_field : str
        Name of the column that contains the date
    query : str
        Query that will be perfored for each request

    Methods
    -------
    load(year=None, nrows=None, offset=0, pbar=True)
        Load data for query
    get_count(year=None, where=None)
        Get number of records/rows generated by query
    get_years()
        Get years contained in data set
    """
    
    def __init__(self, url, data_set, date_field=None, query=None):
        '''Create Opendatasoft object

        Parameters
        ----------
        url : str
            URL or username for Opendatasoft data
        data_set : str
            Dataset ID
        date_field : str
            (Optional) Name of the column that contains the date
        query : str, dict
            (Optional) Additional query that will be added to each request
        '''

        # https://help.opendatasoft.com/apis/ods-explore-v2/

        # https://help.opendatasoft.com/apis/ods-explore-v2/#section/Introduction/Base-URL
        domain = url
        if domain.startswith("https://"):
            domain = domain.replace("https://", "")

        if domain.endswith('/'):
            domain = domain[:-1]

        base_url = "https://" + domain + "/api/explore/v2.1/catalog/datasets"

        self.url = base_url
        self.data_set = data_set
        self.date_field = date_field
        self.query = str2json(query)

    
    def isfile(self):
        '''Returns False to indicate that Opendatasoft data is not file-based

        Returns
        -------
        False
        '''
        return False
    
    def get_api_url(self):
        return f'{self.url}/{self.data_set}/records/'


    def get_count(self, year=None, **kwargs):
        '''Get number of records for a data request
        
        Parameters
        ----------
        year : int, list
            (Optional) Either the year or the year range [first_year, last_year] for the data that is being requested.  None value returns data for all years.
            
        Returns
        -------
        int
            Record count or number of rows in data request
        '''

        if self._last_count is not None and self._last_count[0]==year:
            logger.debug("Request matches previous count request. Returning saved count.")
            return self._last_count[1]
        else:
            where = self.__construct_where(year)
            json = self.__request(where=where, return_count=True)
            count = json["total_count"]

        self._last_count = (year, count, where)

        return count


    def __request(self, where=None, return_count=False, out_fields="*", out_type='csv', offset=0, count=None, pbar=False):

        query = ""
        params = {}
        if return_count:
            params['limit'] = 0  # Count is always returned. Just request 0 rows
            url = self.get_api_url()
        else:
            params['select'] = out_fields
            url = f'{self.url}/{self.data_set}/exports/'+out_type

        if where != None:
            query+=where

        default_where = ''
        for k,v in self.query.items():
            raise NotImplementedError('No previous datasets have queries. Query handling needs to be validated for Opendatasoft data')
            if isinstance(v,str):
                v = f"'{v}'"
            default_where += f" AND {k}={v}"

        if len(default_where):
            if where != None and len(where)>0:
                raise NotImplementedError('No previous datasets have queries. Query handling needs to be validated for Opendatasoft data')
                query+=default_where
            else:
                raise NotImplementedError('No previous datasets have queries. Query handling needs to be validated for Opendatasoft data')
                query+=default_where[4:]

        params['offset'] = offset

        if count!=None:
            params['limit'] = int(count)

        params["where"] = where

        logger.debug(f"Request data from {self.url}")
        for k,v in params.items():
            logger.debug(f"\t{k} = {v}")

        if return_count:
            r = requests.get(url, params=params)

            try:
                r.raise_for_status()
            except requests.HTTPError as e:
                if len(e.args)>0:
                    if "503 Server Error" in e.args[0]:
                        raise OPD_DataUnavailableError(self.get_api_url(), e.args, _url_error_msg.format(self.get_api_url()))
                    else:
                        raise

                else: raise e
            except: raise
            
            return r.json()
        elif out_type.lower()=='csv':
            start = None
            if 'offset' in params and 'limit' in params and params['offset']>0 and \
                (params['offset']+params['limit']>10000 or params['limit']==-1):
                # Although it appears this query should work, it does not:
                # https://community.opendatasoft.com/managing-data-portal-73/invalid-value-for-sum-of-offset-limit-api-parameter-error-with-exports-endpoint-566
                start = params.pop('offset')
                count = params.pop('limit')
            r = requests.get(url, params=params, stream=True)
            r.raise_for_status()
            df = pd.read_csv(TqdmReader(r, pbar=pbar), delimiter=';', low_memory=False)

            if start!=None:
                stop = start+count if count!=-1 else len(df)+1
                df = df.iloc[start:stop].reset_index(drop=True)

            return df
        else:
            raise NotImplementedError(f"Unable to format output type: {out_type}")


    def __construct_where(self, year=None):
        if self.date_field!=None and year!=None:
            start_date, stop_date = _process_date(year, date_field=self.date_field)
            where_query = f"{self.date_field} >= '{start_date}' AND {self.date_field} <= '{stop_date}'"
        else:
            where_query = None

        return where_query

    
    def load(self, year=None, nrows=None, offset=0, *, pbar=True, format_date=True, **kwargs):
        '''Download table to pandas or geopandas DataFrame
        
        Parameters
        ----------
        year : int, list
            (Optional) Either the year or the year range [first_year, last_year] for the data that is being requested.  None value returns data for all years.
        nrows : int
            (Optional) Only returns the first nrows rows of the table
        offset - int
            (Optional) Number of records to offset from first record. Default is 0 to return records starting from the first.
        pbar : bool
            (Optional) If true (default), a progress bar will be displayed
        format_date : bool, optional
            If True, known date columns (based on presence of date_field in datasets table or data type information provided by dataset owner) will be automatically formatted
            to be pandas datetimes (or pandas Period in rare cases), by default True
            
        Returns
        -------
        pandas or geopandas DataFrame
            DataFrame containing downloaded
        '''

        nrows = nrows if nrows!=None else -1

        where_query = self.__construct_where(year)
        df = self.__request(where=where_query, offset=offset, count=nrows, pbar=pbar)
        
        return df

