from abc import ABCMeta, abstractmethod
from datetime import datetime, timedelta
from logging import Logger

import iso8601
from production.model import ProductionOrder
from typing import Optional, Union, List, Callable, Any
from .._ProductionBox import ProductionBox


class StatisticBox(ProductionBox):
    __metaclass__ = ABCMeta

    def __init__(self, name, sons, publish_scheduler, logger, get_config):
        # type: (str, Optional[Union[List[ProductionBox]], ProductionBox], PublishScheduler, Logger, Callable[[str], str]) -> None # noqa

        super(StatisticBox, self).__init__(name, sons, logger)
        self.statistic_name = get_config("StatisticName")
        self.time_period = float(get_config("TimePeriod") or "86400")
        self.start_tag = get_config("StartTagName")
        self.end_tag = get_config("EndTagName")

        self.publish_scheduler = publish_scheduler
        self.orders = {}

    def order_modified(self, order):
        # type: (ProductionOrder) -> None

        order_id = order.order_id
        if self.invalid_order(order):
            if order_id in self.orders:
                del self.orders[order_id]
        else:
            self.orders[order_id] = self.get_order_value(order)

        statistic_value = self.update_statistics(self.orders.values())
        if statistic_value is not None:
            if not hasattr(order, "statistics"):
                order.statistics = {}
            order.statistics.update({self.statistic_name: statistic_value})

        self.send_to_children(order)

    def invalid_order(self, order):
        start_tag = self.get_tag_with_sub_order_id(order, self.start_tag)
        if start_tag not in order.tags:
            return True

        if self.end_tag is not None:
            end_tag = self.get_tag_with_sub_order_id(order, self.end_tag)
            if end_tag not in order.tags:
                return True

        current_time = datetime.now(tz=iso8601.UTC)
        tag_time = iso8601.parse_date(order.tags[start_tag])
        order_elapsed_time = current_time - tag_time

        if order_elapsed_time <= timedelta(seconds=self.time_period):
            time_to_order_expire = self.time_period - order_elapsed_time.total_seconds()
            self.publish_scheduler.schedule_publish(order.order_id, time_to_order_expire)
            return False

        return True

    @staticmethod
    def get_tag_with_sub_order_id(order, tag):
        # type: (ProductionOrder, str) -> str

        sub_order_ids = order.order_id.split("-")[1:]
        sub_order_id = "-{}".format("-".join(sub_order_ids)) if sub_order_ids else ""
        return "{}{}".format(tag, sub_order_id)

    @abstractmethod
    def get_order_value(self, order):
        # type: (ProductionOrder) -> Any
        raise NotImplementedError()

    @abstractmethod
    def update_statistics(self, values):
        # type: (List[Any]) -> float
        raise NotImplementedError()
