import os

import cfgtools

from datetime import (
    date,
    datetime,
)
from typing import List

from actions.rupture.get_ruptured_products import has_rupture_items
from actions.rupture.clean_rupture import clean_rupture_on_open_day

from actions.update import (
    get_catalog_version_to_apply,
    get_message_update,
    apply_update,
)
from helper import translate_pos_label
from msgbus import (
    TK_SYS_ACK,
    MBEasyContext,
)
from sysactions import (
    show_messagebox,
    format_date,
    StopAction,
    get_model,
    get_poslist,
    get_business_period,
    get_podtype,
)

BUTTONS = "$OK|$CANCEL"
total_blocking_days_cache = None


class OpenDayInteractor(object):
    def __init__(
        self,
        pos_list=None,      # type: List
    ):
        # type: (...) -> None

        self.pos_list = pos_list
        self.current_pos_id = None
        self.qa_test_runner = os.environ.get("QA_TEST_RUNNER", False)

        if not self.pos_list:
            self.pos_list = get_poslist()

    def process_before_open_day(
        self,                # type: OpenDayInteractor
        pos_id,              # type: int
        mb_context,          # type: MBEasyContext
    ):
        # type: (...) -> None

        self.current_pos_id = pos_id

        self._check_has_some_opened_day_blocking_opening_business_day()
        self._check_has_menu_update(pos_id=pos_id)
        self._confirm_open_day(pos_id=pos_id)

        if self._is_first_pos_opening_current_day():
            clean_rupture_on_open_day()
            self.send_open_day_event(mb_context=mb_context)

    def _check_has_menu_update(
        self,    # type: OpenDayInteractor
        pos_id,  # type: int
    ):
        # type: (...) -> None

        if self.qa_test_runner:
            return

        msg = get_catalog_version_to_apply()
        if msg.token != TK_SYS_ACK:
            return

        get_message_update(pos_id, "$APPLYING_UPDATE_MENU_VERSION_PENDING|" + msg.data)
        has_rupture_items(pos_id)
        applied_update = apply_update(pos_id)
        if applied_update:
            return

        confirm = show_messagebox(pos_id, message="$UPDATE_MENU_ERROR_PROCEED", buttons=BUTTONS)
        if (confirm is None) or (confirm == 1):
            raise StopAction()

    @staticmethod
    def _confirm_open_day(
        pos_id,  # type: int
    ):
        # type: (...) -> None

        today = date.today().strftime("%Y%m%d")
        confirm_message = "$CONFIRM_OPEN_CURRENT_DAY|{0}".format(format_date(today))
        confirm = show_messagebox(pos_id, message=confirm_message, buttons=BUTTONS)
        if (confirm is None) or (confirm == 1):
            raise StopAction()

    @staticmethod
    def _is_first_pos_opening_current_day():
        # type: (...) -> bool

        pos_list = sorted(get_poslist())
        current_business_period = date.today().strftime("%Y%m%d")

        for pos in pos_list:
            model = get_model(posid=pos)
            business_period = get_business_period(model=model)
            if current_business_period == business_period:
                return False

        return True

    @staticmethod
    def send_open_day_event(
        mb_context,     # type: MBEasyContext
    ):
        # type: (...) -> None

        current_business_period = date.today().strftime("%Y%m%d")
        event_xml = '''<STORE_OPEN businessPeriod="{}"/>'''
        data = event_xml.format(current_business_period)

        mb_context.MB_EasyEvtSend(
            subject="STORE_STATUS",
            type="OPEN_STORE_DAY",
            xml=data,
            synchronous=False,
            timeout=-1,
        )

    def _check_has_some_opened_day_blocking_opening_business_day(
        self,
    ):
        # type: (...) -> None

        total_blocking_days = self._get_total_blocking_days()
        days_open = []
        if self.pos_list is not None:
            for pos_id in sorted(self.pos_list):
                self._append_pos_if_must_block(
                    pos_id=str(pos_id),
                    days_open=days_open,
                    days_blocking=total_blocking_days,
                )

            if len(days_open) > 0:
                days_open = [item.get("pos") for item in sorted(days_open, key=lambda line: line['date'])]
                message = "$BLOCKING_DAYS_FOR_OPENING_BUSINESS_DAY|{0}|{1}".format(
                    total_blocking_days,
                    ", ".join(days_open),
                )

                show_messagebox(
                    posid=self.current_pos_id,
                    message=message,
                )

                raise StopAction()

    @staticmethod
    def _append_pos_if_must_block(
        pos_id,             # type: str
        days_open,          # type: List
        days_blocking,      # type: int
    ):
        # type: (...) -> None

        if pos_id == '0':
            return

        model = get_model(
            posid=pos_id,
        )
        if get_podtype(model) in ("TT", "DL"):
            return

        pos_is_open = model.find("PosState").get("state") not in ("CLOSED", "UNDEFINED")
        if not pos_is_open:
            return

        business_period = get_business_period(
            model=model,
        )
        business_period_date = datetime.strptime(business_period, '%Y%m%d')
        today_date = datetime.today()
        if (today_date-business_period_date).days > days_blocking:
            pos = translate_pos_label(model=model)
            days_open.append(
                {
                    "pos": "{}{}".format(pos, int(pos_id)),
                    "date": business_period,
                }
            )

    @staticmethod
    def _get_total_blocking_days(
    ):
        # type: (...) -> int

        global total_blocking_days_cache

        if total_blocking_days_cache is None:
            config = cfgtools.read(
                file_or_path=os.environ["LOADERCFG"],
            )

            total_blocking_days_cache = int(
                config.find_value(
                    path="Customizations.BlockingDaysForOpeningBusinessDay",
                ) or 3
            )

        return total_blocking_days_cache
