import json
from typing import (
    Optional,
    List,
)

from xml.etree import cElementTree as eTree
from api.order import get_order_service
from bustoken import TK_SLCTRL_OMGR_REVERTFRACTIONLINE
from msgbus import (
    FM_PARAM,
    TK_SYS_ACK,
)
from posot import (
    OrderTaker,
    OrderTakerException,
)

from custom_sysactions import user_authorized_to_execute_action
from mw_helper import (
    show_messagebox,
    show_message_dialog,
)
from sysactions import (
    action,
    get_model,
    get_posot,
    has_current_order,
    send_message,
    StopAction,
)


title = "$INFORMATION"


@action
@user_authorized_to_execute_action
def do_merge_items(
    pos_id,            # type: str
    sale_line_json,    # type: json
):
    # type: (...) -> Optional[bool]
    from msgbusboundary import MsgBusTableService as tableService
    from table_actions import create_table_modified_event

    sale_line_list = json.loads(sale_line_json)
    sale_line_list = filter(lambda x: x.get("level") == 0, sale_line_list)

    model = get_model(posid=pos_id)
    pos_ot = get_posot(model=model)

    try:
        merge_line = _get_merge_line(
            pos_id=pos_id,
            pos_ot=pos_ot,
            sale_line_list=sale_line_list,
        )
        if merge_line is None:
            return False

        _merge_items(
            pos_id=pos_id,
            merge_line=merge_line,
            pos_ot=pos_ot,
        )
    except OrderTakerException as ex:
        show_messagebox(
            posid=pos_id,
            message="$ERROR_CODE_INFO|%d|%s" % (ex.getErrorCode(), ex.getErrorDescr()),
        )
        raise StopAction()
    finally:
        pos_ot.blkopnotify = False

        model = get_model(pos_id)
        if has_current_order(model):
            order_service = get_order_service(pos_id=pos_id)
            order_service.store_order()

    table_id = tableService().get_current_table_id(pos_id)
    table_picture = tableService().get_table_picture(pos_id, table_id)

    create_table_modified_event(pos_id, table_picture)
    return True


def _get_merge_line(
    pos_id,            # type: str
    pos_ot,            # type: OrderTaker
    sale_line_list,    # type: List[dict]
):
    # type: (...) -> Optional[dict]

    invalid_line = _have_invalid_line(
        pos_id=pos_id,
        sale_line_list=sale_line_list,
    )
    if invalid_line:
        return None

    all_parts_is_selected = _check_if_all_parts_is_selected(
        pos_id=pos_id,
        pos_ot=pos_ot,
        sale_line_list=sale_line_list,
    )
    if not all_parts_is_selected:
        return None

    return sale_line_list[0]


def _check_if_all_parts_is_selected(
    pos_id,           # type: str
    pos_ot,           # type: OrderTaker
    sale_line_list,   # type: List[dict]
):
    # type: (...) -> bool

    order_picture = eTree.XML(pos_ot.orderPicture(pos_id, sale_line_list[0]["orderId"]))
    fraction_from = sale_line_list[0]["fractionFrom"]
    sale_lines = order_picture.findall(".//SaleLine")
    count_lines = 0
    for sale_line in sale_lines:
        custom_properties = json.loads(sale_line.get("customProperties"))
        if custom_properties.get("OMGR_FRACTION_FROM_LINE", 0) == fraction_from:
            count_lines += 1

    if len(sale_line_list) != count_lines:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$SELECT_ALL_PARTS",
        )
        return False

    return True


def _have_invalid_line(
    pos_id,            # type: str
    sale_line_list,    # type: List[dict]
):
    # type: (...) -> bool

    order_ids = []
    fractions_from = []
    for sale_line in sale_line_list:
        order_ids.append(int(sale_line["orderId"]))
        fractions_from.append(int(sale_line["fractionFrom"] or 0))

    unique_order_ids = list(set(order_ids))
    unique_fractions_from = list(set(fractions_from))

    if sum(fractions_from) == 0:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$NOT_FRACTIONATED_ITEM",
        )
        return True

    if len(unique_order_ids) > 1:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$CANNOT_MERGE",
        )
        return True

    if len(unique_fractions_from) > 1:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$SELECT_ALL_PARTS",
        )
        return True

    return False


def _merge_items(
    pos_id,        # type: str
    merge_line,    # type: dict
    pos_ot,        # type: OrderTaker
):
    # type: (...) -> None

    order_service = get_order_service(pos_id=pos_id)
    order_service.recall_order(order_id=merge_line["orderId"])
    block_notify = "1"
    data = "\0".join([
        block_notify,
        str(pos_id),
        str(merge_line['orderId']),
        str(merge_line["fractionFrom"]),
    ])
    msg = send_message(
        dest_name="ORDERMGR{}".format(pos_id),
        token=TK_SLCTRL_OMGR_REVERTFRACTIONLINE,
        format=FM_PARAM,
        data=data,
        timeout=60 * 1000000,
    )
    if msg.token != TK_SYS_ACK:
        show_message_dialog(
            pos_id=pos_id,
            title="$FATAL_ERROR_CALL_SUPPORT",
            message=msg.data,
        )
        raise StopAction()

    order_service.store_order()
