# -*- coding: utf-8 -*-
import copy
from datetime import datetime
from typing import List, Dict, Tuple, Iterator, Union, Generator, Optional

import hxl
from hdx.location.adminone import AdminOne
from hdx.location.country import Country
from hdx.utilities.dateparse import parse_date
from hdx.utilities.dictandlist import dict_of_lists_add

from hdx.scraper import match_template


class RowParser(object):
    """RowParser class for parsing each row.

    Args:
        countryiso3s (List[str]): List of ISO3 country codes to process
        adminone (AdminOne): AdminOne object from HDX Python Country library that handles processing of admin level 1
        level (str): Can be global, national or subnational
        datasetinfo (Dict): Dictionary of information about dataset
        headers (List[str]): Row headers
        subsets (List[Dict]): List of subset definitions
        maxdateonly (bool): Whether to only take the most recent date. Defaults to True.
    """

    def __init__(self, countryiso3s, adminone, level, datasetinfo, headers, subsets, maxdateonly=True):
        # type: (List[str], AdminOne, str, Dict, List[str], List[Dict], bool) -> None
        if isinstance(level, str):
            if level == 'global':
                level = None
            elif level == 'national':
                level = 0
            else:
                level = 1
        self.level = level
        self.datecol = datasetinfo.get('date_col')
        self.datetype = datasetinfo.get('date_type')
        if self.datetype:
            if self.datetype == 'date':
                date = parse_date('1900-01-01')
            else:
                date = 0
        else:
            date = 0
        self.maxdate = date
        date_condition = datasetinfo.get('date_condition')
        if date_condition is not None:
            for col in datasetinfo['input_cols']:
                date_condition = date_condition.replace(col, f"row['{col}']")
        self.date_condition = date_condition
        self.adminone = adminone
        self.admcols = datasetinfo.get('adm_cols', list())
        self.admexact = datasetinfo.get('adm_exact', False)
        self.subsets = subsets
        self.adms = [countryiso3s, self.adminone.pcodes]
        if self.level is None:
            self.maxdates = {i: date for i, _ in enumerate(subsets)}
        else:
            if self.level > len(self.admcols):
                raise ValueError('No admin columns specified for required level!')
            self.maxdates = {i: {adm: date for adm in self.adms[self.level]} for i, _ in enumerate(subsets)}

        self.maxdateonly = maxdateonly
        self.flatteninfo = datasetinfo.get('flatten')
        self.headers = headers
        self.filters = dict()
        self.read_external_filter(datasetinfo)

    def read_external_filter(self, datasetinfo):
        # type: (Dict) -> Tuple[List[str],Iterator[Union[List,Dict]]]
        """Read filter list from external url poitning to a HXLated file

        Args:
            datasetinfo (Dict): Dictionary of information about dataset

        Returns:
            None
        """
        external_filter = datasetinfo.get('external_filter')
        if not external_filter:
            return
        hxltags = external_filter['hxltags']
        data = hxl.data(external_filter['url'])
        for row in data:
            for hxltag in data.columns:
                if hxltag.display_tag in hxltags:
                    dict_of_lists_add(self.filters, hxltag.header, row.get('#country+code'))

    def flatten(self, row):
        # type: (Dict) -> Generator[Dict]
        """Flatten a wide spreadsheet format into a long one

        Args:
            row (Dict): Row to flatten

        Returns:
            Generator[Dict]: Flattened row(s)
        """
        if not self.flatteninfo:
            yield row
            return
        counters = [-1 for _ in self.flatteninfo]
        while True:
            newrow = copy.deepcopy(row)
            for i, flatten in enumerate(self.flatteninfo):
                colname = flatten['original']
                template_string, replace_string = match_template(colname)
                if not template_string:
                    raise ValueError('Column name for flattening lacks an incrementing number!')
                if counters[i] == -1:
                    counters[i] = int(replace_string)
                else:
                    replace_string = '%d' % counters[i]
                colname = colname.replace(template_string, replace_string)
                if colname not in row:
                    return
                newrow[flatten['new']] = row[colname]
                extracol = flatten.get('extracol')
                if extracol:
                    newrow[extracol] = colname
                counters[i] += 1
            yield newrow

    def get_maxdate(self):
        # type: () -> datetime
        """Get the most recent date of the rows so far

        Returns:
            datetime: Most recent date in processed rows
        """
        return self.maxdate

    def filtered(self, row):
        # type: (Dict) -> bool
        """Check if the row should be filtered out

        Args:
            row (Dict): Row to check for filters

        Returns:
            bool: Whether row is filtered out or not
        """
        for header in self.filters:
            if header not in row:
                continue
            if row[header] not in self.filters[header]:
                return True
        return False

    def parse(self, row, scrapername=None):
        # type: (Dict, str) -> Tuple[Optional[str], Optional[List[bool]]]
        """Parse row checking for valid admin information and if the row should be filtered out in each subset given
        its definition.

        Args:
            row (Dict): Row to parse

        Returns:
            Tuple[Optional[str], Optional[List[bool]]]: (admin name, should process subset list) or (None, None)
        """
        if self.filtered(row):
            return None, None

        adms = [None for _ in range(len(self.admcols))]

        def get_adm(admcol, i):
            template_string, match_string = match_template(admcol)
            if template_string:
                admcol = self.headers[int(match_string)]
            adm = row[admcol]
            if not adm:
                return False
            adm = adm.strip()
            adms[i] = adm
            if adm in self.adms[i]:
                return True
            exact = False
            if self.admexact:
                adms[i] = None
            else:
                if i == 0:
                    adms[i], exact = Country.get_iso3_country_code_fuzzy(adm)
                elif i == 1:
                    adms[i], exact = self.adminone.get_pcode(adms[0], adm, scrapername)
                if adms[i] not in self.adms[i]:
                    adms[i] = None
            return exact

        for i, admcol in enumerate(self.admcols):
            if admcol is None:
                continue
            if isinstance(admcol, str):
                admcol = [admcol]
            for admcl in admcol:
                exact = get_adm(admcl, i)
                if adms[i] and exact:
                    break
            if not adms[i]:
                return None, None

        should_process_subset = list()
        for subset in self.subsets:
            filter = subset['filter']
            process = True
            if filter:
                filters = filter.split('|')
                match = True
                for filterstr in filters:
                    filter = filterstr.split('=')
                    if row[filter[0]] != filter[1]:
                        match = False
                        break
                process = match
            should_process_subset.append(process)

        if self.datecol:
            if isinstance(self.datecol, list):
                dates = [str(row[x]) for x in self.datecol]
                date = ''.join(dates)
            else:
                date = row[self.datecol]
            if self.datetype == 'date':
                if not isinstance(date, datetime):
                    date = parse_date(date)
                date = date.replace(tzinfo=None)
            else:
                date = int(date)
            if self.date_condition:
                if eval(self.date_condition) is False:
                    return None, None
            for i, process in enumerate(should_process_subset):
                if not process:
                    continue
                if date > self.maxdate:
                    self.maxdate = date
                if self.level is None:
                    if self.maxdateonly:
                        if date < self.maxdates[i]:
                            should_process_subset[i] = False
                        else:
                            self.maxdates[i] = date
                    else:
                        self.maxdates[i] = date
                else:
                    if self.maxdateonly:
                        if date < self.maxdates[i][adms[self.level]]:
                            should_process_subset[i] = False
                        else:
                            self.maxdates[i][adms[self.level]] = date
                    else:
                        self.maxdates[i][adms[self.level]] = date
        if self.level is None:
            return 'global', should_process_subset
        return adms[self.level], should_process_subset
