from collections import defaultdict
import logging
import itertools

from django.utils import timezone
from django.utils.functional import cached_property
from statface_client import DAILY_SCALE

from staff.stats.utils import (
    get_beginning_of_day,
    str_to_date,
    DepartmentUnit,
)
from staff.lib.models import departments_chain


logger = logging.getLogger(__name__)


class ReportDataFetcher:

    dt_format = '%Y-%m-%d'
    report_name = None  # Название отчета в URL
    title = None  # Человекочитаемое название отчета
    config_name = None
    scale = DAILY_SCALE
    delta = None

    def __init__(self, config, fielddate=None, delta=None):
        self.config = config
        self._setup_dates(fielddate, delta)

    def _setup_dates(self, fielddate, delta):
        if fielddate is None:
            self.to_dt = get_beginning_of_day(timezone.now())
            yesterday = self.to_dt - timezone.timedelta(days=1)
            self.fielddate = yesterday.strftime(self.dt_format)
        else:
            self.fielddate = fielddate
            self.to_dt = str_to_date(fielddate) + timezone.timedelta(days=1)

        if delta is not None:
            self.from_dt = self.to_dt - delta
        else:
            self.from_dt = self.to_dt - timezone.timedelta(days=1)

    @cached_property
    def dimensions(self):
        result = self.config.dimensions.copy()
        result.pop('fielddate')
        return result

    @cached_property
    def measures(self):
        return self.config.measures

    def get_data(self):
        return {}


class HierarchicReportDataFetcher(ReportDataFetcher):
    """
    Умеет агрегировать данные по дереву департаментов (1) и по измерениям (2).
    (1) Для измерения departament показатели аггрегируются вверх по иерархии.
    (2) Для измерений с несколькими значеними добавляетя значение ALL
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.initialize_result()

    def initialize_result(self):
        """
        Инициализируем словарь, в котором будут содержаться все полученные данные.
        Ключи словаря - кортежы срезов
        Значения - словари с показателями
        """
        self.result = defaultdict(lambda: {m: 0 for m in self.measures})

    @cached_property
    def dep_ancestors_map(self):
        return departments_chain.get_departments_tree(fields=('id',))

    def get_related_keys(self, key):
        """
        Для каждого кортежа со срезом получаем все связанные срезы, которые так же нужно учесть
        """
        assert len(key) == len(self.dimensions)

        dimensions = []
        for item in key:
            if isinstance(item, DepartmentUnit):
                dimensions.append(self.get_department_units_chain(item))
            else:
                dimensions.append([item, 'ALL'])
        return itertools.product(*dimensions)

    def get_department_units_chain(self, department_unit):
        """
        :type department_unit: DepartmentUnit
        :return: Восстановленный к корню список подразделений
        """
        result = [
            DepartmentUnit(dep['id'])
            for dep in self.dep_ancestors_map[department_unit.id]
        ]
        return result

    def get_transformed_item(self, key):
        """
        Ключ-значение из преподсчитанного словаря трансформирует в строку для аплоада в Stat
        """
        result = {
            'fielddate': self.fielddate,
        }
        result.update(self.get_transformed_dimensions(key))
        result.update(self.get_transformed_measures(key))

        return result

    def get_transformed_dimensions(self, key):
        result = {}
        for item, (name, _type) in zip(key, self.dimensions.items()):
            if isinstance(item, DepartmentUnit):
                result[name] = [node.id for node in self.get_department_units_chain(item)]
            else:
                result[name] = item
        return result

    def get_transformed_measures(self, key):
        return self.result[key]

    def collect_data(self):
        raise NotImplementedError

    def get_data(self):
        self.collect_data()
        return [self.get_transformed_item(i) for i in self.result]
