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

import iso8601
from mw_helper import ensure_iterable, ensure_list
from production.box import ProductionBox
from production.box._OrderChangerProductionBox import OrderChangerProductionBox
from production.model import (
    ProductionOrder,
    ProdStates,
)
from typing import Optional, Union, List


def _get_order_time(
    order,                  # type: ProductionOrder
    time_type="display",    # type: Optional[str]
):
    # type: (...) -> str

    selected_time = order.display_time
    if time_type == "in_preparation":
        is_delivered = order.prod_state == ProdStates.DELIVERED
        selected_time = order.prod_state_last_update if is_delivered else order.properties.get("FISCALIZATION_DATE", "")

    if selected_time == "":
        selected_time = order.created_at
    return selected_time


class OrderSequencerBox(OrderChangerProductionBox):
    def __init__(self, name, sons, sorters, logger=None):
        # type: (str, Optional[Union[List[ProductionBox]], ProductionBox], Optional[Union[List[OrderSorter]]], Logger) -> None # noqa
        super(OrderSequencerBox, self).__init__(name, sons, logger)
        self.sorters = ensure_iterable(sorters)  # type: List[OrderSequencerBox.OrderSorter]

    def change_order(self, order):
        sequence = ""
        for sorter in self.sorters:
            sequence += sorter.create_prod_sequence(order)

        order.prod_sequence = sequence
        return order

    class OrderSorter(object):
        __metaclass__ = ABCMeta

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

    class PriorityCustomerSorter(OrderSorter):
        def create_prod_sequence(self, order):
            if "PRIORITY" in order.properties and order.properties["PRIORITY"] == "true":
                return "00"
            for item in order.items:
                if 'highPriority' in item.properties:
                    return "10"
            return "99"

    class PodTypeSorter(OrderSorter):
        def __init__(self, pod_type_order):
            # type: (Optional[Union[List[str], str]]) -> None
            self.pod_type_order = ensure_list(pod_type_order)

        def create_prod_sequence(self, order):
            try:
                index = self.pod_type_order.index(order.pod_type)
                return str(index).zfill(2)
            except ValueError:
                return "99"

    class DoneInFrontSorter(OrderSorter):
        def create_prod_sequence(self, order):
            if order.tagged_timestamp is not None:
                return "00" + order.tagged_timestamp
            else:
                return "99"

    class SaleTypeSorter(OrderSorter):
        def __init__(self, sale_type_order):
            self.sale_type_order = ensure_list(sale_type_order)

        def create_prod_sequence(self, order):
            try:
                index = self.sale_type_order.index(order.sale_type)
                return str(index).zfill(2)
            except ValueError:
                return "99"

    class OrderIdSorter(OrderSorter):
        def create_prod_sequence(self, order):
            return str(order.order_id)

    class OrderStateSorter(OrderSorter):
        def __init__(self, state_order):
            self.state_order = ensure_list(state_order)

        def create_prod_sequence(self, order):
            try:
                index = self.state_order.index(order.state)
                return str(index).zfill(2)
            except ValueError:
                return "99"

    class LastModifiedSorter(OrderSorter):
        def __init__(self, last_modified_first=False):
            super(OrderSequencerBox.LastModifiedSorter, self).__init__()
            self.last_modified_first = last_modified_first

        def create_prod_sequence(self, order):
            if self.last_modified_first:
                future_date = iso8601.parse_date("3000-02-13T00:00:00Z")
                past_date = iso8601.parse_date("1984-02-13T00:00:00Z")
                last_change_date = iso8601.parse_date(order.state_history[-1].timestamp)
                return past_date + timedelta(seconds=(future_date - last_change_date).total_seconds())
            else:
                return order.state_history[-1].timestamp

    class DisplayTimeSorter(OrderSorter):
        def __init__(self, invert=True):
            self.invert = invert

        def create_prod_sequence(self, order):
            display_time = _get_order_time(order=order)

            if display_time and self.invert:
                future_date = iso8601.parse_date("3000-02-13T00:00:00Z")
                past_date = iso8601.parse_date("1984-02-13T00:00:00Z")
                last_change_date = iso8601.parse_date(display_time)

                sequence_date = (past_date + timedelta(seconds=(future_date - last_change_date).total_seconds()))
                return sequence_date.strftime("%Y%m%d%H%M%S")

            return display_time

    class DoneSorter(OrderSorter):
        def create_prod_sequence(self, order):
            return ""

    class InPreparationTimeSorter(OrderSorter):
        def __init__(
            self,
            invert=True,    # type: Optional[bool]
        ):
            # type: (...) -> None

            self.invert = invert

        def create_prod_sequence(
            self,
            order,  # type: ProductionOrder
        ):
            # type: (...) -> str

            in_preparation_time = _get_order_time(
                order=order,
                time_type="in_preparation",
            )

            if in_preparation_time and self.invert:
                future_date = iso8601.parse_date("3000-02-13T00:00:00Z")
                past_date = iso8601.parse_date("1984-02-13T00:00:00Z")
                last_change_date = iso8601.parse_date(in_preparation_time)

                sequence_date = (past_date + timedelta(seconds=(future_date - last_change_date).total_seconds()))
                return sequence_date.strftime("%Y%m%d%H%M%S")

            return in_preparation_time
