import json
import logging

from helper import remove_accents
from mw_helper import (
    show_keyboard,
    show_message_dialog,
    show_tab_customer_dialog,
)
from pos_model import TableStatusDescr
from sysactions import (
    action,
    get_model,
    get_posfunction,
    show_confirmation,
    show_messagebox,
)
from xml.etree import cElementTree as eTree

from actions.table.create_table_order import create_table_order
from api.table import get_table_service
from application.interactor import (
    StartServiceInteractor,
    TabsInteractor,
)
from application.interactor.service.impl import TableReleaserImpl
from custom_sysactions import (
    block_action_if_pos_is_blocked,
    user_must_be_logged,
)
from model.customexception import TabAlreadyExists
from msgbusboundary import MsgBusTableService as tableService
from msgbusboundary.ui import MsgBusScreenChanger
from table_manager_api.dto.exception import (
    InvalidTabMask,
    TabNotFound,
    TableError,
)
from typing import (
    Dict,
    Optional,
    Union,
)
from utilfunctions import is_valid_string

logger = logging.getLogger("TableActions")


@action
def show_open_tabs(
    pos_id,     # type: int
):
    # type: (...) -> bool

    tabs_interactor = TabsInteractor(int(pos_id))
    opened_tabs = tabs_interactor.get_opened_tabs()
    if not opened_tabs:
        show_messagebox(pos_id, "$EMPTY_OPENED_TABS")
        return False

    tabs_interactor.show_opened_tabs(opened_tabs)

    return True


@action
def read_tab_name(
    pos_id,     # type: str
    name,       # type: str
):
    # type: (...) -> bool

    pos_function = _get_pos_function(pos_id=pos_id)
    tab_picture = None
    try:
        tab_picture = get_table_service(pos_id=pos_id).get_service_tab(tab_name=name)
    except InvalidTabMask:
        show_messagebox(
            posid=pos_id,
            message="$TAB_NOT_FOUND",
        )
        return False
    except TabNotFound as ex:
        if pos_function == "CS":
            show_messagebox(
                posid=pos_id,
                message="$THIS_POS_IS_JUST_TO_PAY_COULD_NOT_OPEN_NOT_OPENED_TABS|{}".format(name),
            )
            return False

        name = ex.tab_name

    tab_was_created = False
    if tab_picture is None:
        tab_picture_str = open_tab(
            pos_id=pos_id,
            tab_info={"TAB_NUMBER": name},
        )
        tab_picture = eTree.XML(tab_picture_str)
        tab_id = tab_picture.get("id")
        tab_was_created = True
    else:
        tab_picture = tab_picture.to_xml()
        opened_in_other_pos = _tab_is_opened_in_other_pos(
            pos_id=pos_id,
            tab_picture=tab_picture,
        )
        if opened_in_other_pos:
            return False

        tab_id = tab_picture.get("id")

    if pos_function == "OT":
        if tab_was_created:
            return create_table_order(
                pos_id=pos_id,
                table_id=tab_id,
            )

        return _open_tab_in_order_taker_mode(
            pos_id=pos_id,
            tab_id=tab_id,
            tab_picture=tab_picture,
        )

    if pos_function == "CS":
        return _open_tab_in_cashier_mode(
            pos_id=pos_id,
            tab_id=tab_id,
            tab_picture=tab_picture,
        )

    if pos_function == "FL":
        if tab_was_created:
            return True

        _open_tab_in_full_mode(
            pos_id=pos_id,
            tab_id=tab_id,
            tab_picture=tab_picture,
        )

    return True


@action
@block_action_if_pos_is_blocked
@user_must_be_logged
def open_tab(
    pos_id,                 # type: str
    parent_table_id=None,   # type: Optional[str]
    tab_info=None,          # type: Optional[Dict[str, Union[str, int]]]
):
    # type: (...) -> Union[str, bool]

    from table_actions import (
        allowed_to_create_table,
        create_table_modified_event,
    )

    if parent_table_id is None and not allowed_to_create_table(
        pos_id=pos_id,
        table_id="",
        is_tab=True,
    ):
        show_message_dialog(
            pos_id=pos_id,
            message="$OPERATOR_DOES_NOT_BELONG_TO_TABLE_SECTOR",
        )
        return False

    if not tab_info:
        tab_info = get_tab_info(pos_id=pos_id)
    if not tab_info:
        return False

    tab_id = tab_info.pop("TAB_NUMBER", None)
    if not tab_id:
        return False

    table_releaser = TableReleaserImpl(table_service=tableService())
    screen_changer = MsgBusScreenChanger()

    interactor = StartServiceInteractor(
        screen_changer=screen_changer,
        table_service=tableService(),
        table_releaser=table_releaser,
    )
    parent_table_id = parent_table_id or ""
    table_picture = interactor.execute(
        pos_id=pos_id,
        tab_id=tab_id,
        source_table_id=parent_table_id,
        custom_properties=tab_info,
    )

    create_table_modified_event(
        pos_id=pos_id,
        table_picture=table_picture,
    )

    return table_picture


@action
@user_must_be_logged
def join_tabs(
    pos_id,     # type: str
    tab_id,     # type: Optional[str]
):
    # type: (...) -> None

    user_id = tableService().get_user_id(model=get_model(posid=pos_id))

    while True:
        tab_name = show_keyboard(
            posid=pos_id,
            message="$TYPE_TAB_ID",
        )

        if not tab_name:
            return

        tab_id = _join_tabs(
            pos_id=pos_id,
            current_tab_id=tab_id,
            tab_name_to_join=tab_name,
            user_id=user_id,
        )


def _join_tabs(
    pos_id,             # type: str
    current_tab_id,     # type: str
    tab_name_to_join,   # type: str
    user_id,            # type: str
):
    # type: (...) -> str

    from table_actions import create_table_modified_event

    try:
        table_picture_dto = get_table_service(pos_id=pos_id).join_tabs(
            user_id=user_id,
            current_tab_id=current_tab_id,
            tab_to_join=tab_name_to_join,
        )

        create_table_modified_event(
            pos_id=pos_id,
            table_picture=eTree.tostring(table_picture_dto.to_xml()),
        )

        return table_picture_dto.table.name

    except TableError as ex:
        show_messagebox(
            posid=pos_id,
            message=str(ex),
        )
        logger.info("Error trying to join tab {} to tab ID {}: ".format(tab_name_to_join, current_tab_id))

        return current_tab_id

    except (Exception,):
        show_messagebox(
            posid=pos_id,
            message="$JOIN_TABLES_ERROR",
        )
        logger.exception("Error join tab {} to tab ID {}".format(tab_name_to_join, current_tab_id))

        return current_tab_id


def _get_pos_function(
    pos_id,     # type: str
):
    # type: (...) -> str

    model = get_model(posid=pos_id)
    return get_posfunction(model=model)


def get_tab_info(
    pos_id,     # type: str
):
    # type: (...) -> Optional[Dict[str, str]]

    message = None

    while True:
        if message:
            show_message_dialog(
                pos_id=pos_id,
                message=message,
            )

        customer_data = _get_tab_input(pos_id=pos_id)
        if not customer_data:
            return None

        tab_id = customer_data["TAB_NUMBER"]
        if tab_id is None:
            return None

        message = _get_invalid_data_message(
            customer_data=customer_data,
            tab_id=tab_id,
        )
        if message:
            continue

        try:
            tableService().tab_id_is_valid(
                pos_id=pos_id,
                tab_id=tab_id,
            )
        except TabAlreadyExists:
            message = "$TAB_ALREADY_EXISTS"
            continue
        except (Exception,):
            logger.exception("Error checking if tab {} is valid".format(tab_id))
            return None

        break
    return customer_data


def _get_tab_input(
    pos_id,         # type: str
):
    # type: (...) -> Optional[Dict[str, str]]

    from table_actions import customer_info_tab

    if customer_info_tab:
        customer = show_tab_customer_dialog(pos_id=pos_id)
        if customer is None:
            return None

        customer_data = _format_customer_data(customer=customer)
    else:
        customer_data = {"TAB_NUMBER": None}
        tab_id = show_keyboard(
            posid=pos_id,
            message="$TYPE_TAB_ID",
            defvalue="",
            title="",
            numpad=False,
            buttons="$OK|$PREVIOUS",
        )
        if tab_id is not None:
            customer_data["TAB_NUMBER"] = remove_accents(text=tab_id)

    return customer_data


def _are_all_values_filled(
    obj_dict,   # type: Dict[str, Optional[str]]
):
    # type: (...) -> bool

    return all([False for value in obj_dict.values() if len(value) == 0])


def _get_invalid_data_message(
    customer_data,  # type: Dict[str, str]
    tab_id,         # type: str
):
    # type: (...) -> str

    from table_actions import (
        customer_info_tab,
        max_number_of_tab_digits,
    )

    has_all_values = _are_all_values_filled(obj_dict=customer_data)
    if not has_all_values:
        return "$INVALID_COSTUMER_INFO"

    if not is_valid_string(string=tab_id) or len(tab_id) > max_number_of_tab_digits:
        return "$INVALID_TAB_NAME|{}".format(max_number_of_tab_digits)

    if "customer_phone" in customer_info_tab and len(customer_data["CUSTOMER_PHONE"]) < 10:
        return "$INVALID_CUSTOMER_PHONE"

    if "customer_name" in customer_info_tab and not is_valid_string(string=customer_data["CUSTOMER_NAME"]):
        return "$INVALID_CUSTOMER_NAME"

    return ""


def _format_customer_data(
    customer,   # type: str
):
    # type: (...) -> Dict[str, str]

    from table_actions import customer_info_tab

    customer_data = json.loads(customer)
    if "customer_name" in customer_info_tab:
        customer_data["CUSTOMER_NAME"] = remove_accents(customer_data["CUSTOMER_NAME"])

    customer_data["TAB_NUMBER"] = remove_accents(customer_data["TAB_NUMBER"])

    return customer_data


def _tab_is_opened_in_other_pos(
    pos_id,         # type: str
    tab_picture,    # type: eTree.Element
):
    # type: (...) -> bool

    table_pos_id = tab_picture.get("currentPOSId")
    if table_pos_id and table_pos_id != pos_id:
        show_messagebox(
            posid=pos_id,
            message="$TABLE_OPENED_IN_ANOTHER_POS|{}".format(table_pos_id)
        )
        return True

    if tab_picture.get("sliceParentTableId"):
        tab_id = tab_picture.find("Properties").get("TAB_ID")
        show_messagebox(
            posid=pos_id,
            message="$COULD_NOT_SELECT_TAB_BECAUSE_IS_LINKED_TO_ANOTHER_TAB|{}".format(tab_id),
        )
        return True

    return False


def _open_tab_in_full_mode(
    pos_id,         # type: str
    tab_id,         # type: str
    tab_picture,    # type: Optional[eTree.Element]
):
    # type: (...) -> None

    from actions.table import reopen_table
    from table_actions import select_table

    if tab_picture.get("statusDescr") == TableStatusDescr.TOTALED:
        reopen_table(
            pos_id=pos_id,
            table_id=tab_id,
        )

        return

    select_table(
        pos_id=pos_id,
        table_id=tab_id,
    )


def _open_tab_in_order_taker_mode(
    pos_id,             # type: str
    tab_id,             # type: str
    tab_picture=None,   # type: Optional[eTree.Element]
):
    # type: (...) -> bool

    _open_tab_in_full_mode(
        pos_id=pos_id,
        tab_id=tab_id,
        tab_picture=tab_picture,
    )

    create_table_order(
        pos_id=pos_id,
        table_id=tab_id,
    )
    return True


def _open_tab_in_cashier_mode(
    pos_id,             # type: str
    tab_id,             # type: str
    tab_picture=None,   # type: Optional[eTree.Element]
):
    # type: (...) -> bool

    from table_actions import (
        abandon_table,
        do_total_table,
        select_table,
    )
    from actions.table import deselect_table

    success = select_table(
        pos_id=pos_id,
        table_id=tab_id,
    )
    if not success:
        return False

    if tab_picture.get("statusDescr") == TableStatusDescr.TOTALED:
        return True

    try:
        do_total_table(
            pos_id=pos_id,
            table_id=tab_id,
            display_error=False,
        )
        return True

    except Exception as ex:
        if "$COULD_NOT_TOTALIZE_EMPTY_TABLE" in str(ex) or "$INVALID_TABLE_STATUS" in str(ex):
            logger.info("Could not totalize empty tab {}".format(tab_id))
            proceed = show_confirmation(
                posid=pos_id,
                message="$WANT_TO_CLOSE_TAB_BECAUSE_IS_OPENED_BUT_HAS_NOT_ORDERS",
            )
            if proceed:
                return abandon_table(
                    pos_id=pos_id,
                    table_id=tab_id,
                )

        deselect_table(pos_id=pos_id)

    return False
