import logging

from sysactions import (
    action,
    close_asynch_dialog,
    get_model,
    get_posfunction,
    show_confirmation,
    show_messagebox,
)
from xml.etree import cElementTree as eTree

from actions.login.unpause_user import unpause_user
from actions.table.create_table_order import create_table_order
from api.table import get_table_service
from order_api.dto import OrderState
from table_manager_api import TableService
from table_manager_api.dto import (
    TableServiceDto,
    TableStatus,
)
from table_manager_api.dto.exception import (
    InvalidTabMask,
    InvalidTabName,
    TabNotFound,
    TabNotInValidTabNames,
)
from thread_local import get_current_model
from typing import (
    Optional,
    Tuple,
)

logger = logging.getLogger("TableActions")


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

    pos_id = int(pos_id)
    table_service = get_table_service(pos_id=pos_id)
    try:
        tab_service_dto = table_service.get_service_tab(tab_name=tab_name)
        return eTree.tostring(tab_service_dto.to_xml())
    except (Exception,):
        logger.info("Cannot obtain existing service for tab '{}'".format(tab_name))
        show_messagebox(
            posid=pos_id,
            message="$TAB_DOES_NOT_HAVE_STARTED_SERVICE",
        )
        return None


@action
def read_tab_name(
    pos_id,         # type: str
    name,           # type: str
    user_id=None,   # type: Optional[str]
):
    # type: (...) -> bool

    if user_id:
        logged_user = unpause_user(
            pos_id=pos_id,
            user_id=user_id,
        )
        if not logged_user:
            return False

    pos_id = int(pos_id)
    pos_function = get_pos_function()
    table_service = get_table_service(pos_id=pos_id)

    tab_service_dto, stop_process = get_tab_by_name(
        pos_id=pos_id,
        name=name,
        pos_function=pos_function,
        table_service=table_service
    )
    if stop_process:
        return True

    if tab_service_dto is None:
        tab_service_dto = table_service.start_tab(tab_name=name)
        return _execute_action_for_new_tab(
            pos_id=pos_id,
            pos_function=pos_function,
            tab_service_dto=tab_service_dto,
        )

    opened_in_other_pos, tab_service_dto = _tab_is_opened_in_other_pos(
        pos_id=pos_id,
        tab_service_dto=tab_service_dto,
        table_service=table_service,
    )
    if opened_in_other_pos:
        return False

    return _execute_action_for_existing_tab(
        pos_id=pos_id,
        table_service=table_service,
        pos_function=pos_function,
        tab_service_dto=tab_service_dto,
    )


def get_pos_function():
    # type: (...) -> str

    model = get_current_model()
    return get_posfunction(model=model)


def get_tab_by_name(
    pos_id,         # type: int
    name,           # type: str
    pos_function,   # type: str
    table_service,  # type: TableService
):
    # type: (...) -> Tuple[Optional[TableServiceDto], bool]

    tab_service_dto = None
    stop_process = False

    try:
        tab_service_dto = table_service.get_service_tab(tab_name=name)
    except (InvalidTabMask, TabNotInValidTabNames) as ex:
        show_messagebox(
            posid=pos_id,
            message=ex.message.split("|")[0],
        )
        stop_process = True

    except TabNotFound:
        if pos_function == "CS":
            tab_service_dto = _create_order_in_pos_cs(
                pos_id=pos_id,
                table_service=table_service,
                tab_name=name,
            )
            stop_process = True

    except InvalidTabName:
        show_messagebox(
            posid=pos_id,
            message="$EXISTS_AS_A_TABLE",
        )
        stop_process = True

    return tab_service_dto, stop_process


def _create_order_in_pos_cs(
    pos_id,                 # type: int
    table_service,          # type: TableService
    tab_name=None,          # type: Optional[str]
    tab_service_dto=None,   # type: Optional[TableServiceDto]
):
    # type: (...) -> Optional[TableServiceDto]

    from table_actions import (
        abandon_table,
        create_table_modified_event,
    )

    proceed = show_confirmation(
        posid=pos_id,
        message="$EMPTY_TAB_WANT_TO_ADD_ITEMS",
    )
    if not proceed:
        if tab_service_dto:
            abandon_table(
                pos_id=pos_id,
                table_id=tab_service_dto.table.table_id,
            )
        return None

    if not tab_service_dto:
        tab_service_dto = table_service.start_tab(tab_name=tab_name)
        create_table_modified_event(
            pos_id=pos_id,
            table_picture=eTree.tostring(tab_service_dto.to_xml()),
        )

    create_table_order(
        pos_id=pos_id,
        table_id=tab_service_dto.table.table_id,
    )

    return tab_service_dto


def _execute_action_for_new_tab(
    pos_id,             # type: int
    pos_function,       # type: str
    tab_service_dto,    # type: TableServiceDto
):
    # type: (...) -> bool

    if pos_function == "OT":
        create_table_order(
            pos_id=pos_id,
            table_id=tab_service_dto.table.table_id,
        )

    from table_actions import create_table_modified_event

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

    return True


def _execute_action_for_existing_tab(
    pos_id,             # type: int
    table_service,      # type: TableService
    pos_function,       # type: str
    tab_service_dto,    # type: TableServiceDto
):
    # type: (...) -> bool

    if pos_function == "OT":
        return _open_tab_in_order_taker_mode(
            pos_id=pos_id,
            tab_service_dto=tab_service_dto,
        )

    if pos_function == "CS":
        return _open_tab_in_cashier_mode(
            pos_id=pos_id,
            table_service=table_service,
            tab_service_dto=tab_service_dto,
        )
    if pos_function == "FL":
        _open_tab_in_full_mode(
            pos_id=pos_id,
            tab_service_dto=tab_service_dto,
        )

    return True


def _tab_is_opened_in_other_pos(
    pos_id,             # type: int
    tab_service_dto,    # type: TableServiceDto
    table_service,      # type: TableService
):
    # type: (...) -> Tuple[bool, Optional[TableServiceDto]]

    updated_tab_service_dto = None
    tab_in_another_pos = True
    if tab_service_dto.parent_table_id:
        show_messagebox(
            posid=pos_id,
            message="$COULD_NOT_SELECT_TAB_BECAUSE_IS_LINKED_TO_ANOTHER_TAB|{}".format(tab_service_dto.table.name),
        )
        return tab_in_another_pos, updated_tab_service_dto

    tab_pos_id = _get_table_pos_id(table_service_dto=tab_service_dto)
    if tab_pos_id and tab_pos_id != pos_id:
        success = _close_tab_in_other_pos(
            pos_id=pos_id,
            tab_pos_id=tab_pos_id,
        )
        if not success:
            return tab_in_another_pos, updated_tab_service_dto

        updated_tab_service_dto = table_service.get_service(table_id=tab_service_dto.table.table_id)

    tab_in_another_pos = False
    updated_tab_service_dto = updated_tab_service_dto or tab_service_dto
    return tab_in_another_pos, updated_tab_service_dto


def _close_tab_in_other_pos(
    pos_id,         # type: int
    tab_pos_id,     # type: int
):
    # type: (...) -> bool

    proceed = show_confirmation(
        posid=pos_id,
        message="$TAB_OPENED_IN_ANOTHER_POS_DO_YOU_WANT_TO_RECOVER_HERE|{}".format(tab_pos_id),
    )
    if not proceed:
        return False

    wait_dialog_id = show_messagebox(
        posid=pos_id,
        message="$WAITING_FOR_OPERATOR_CONFIRMATION",
        buttons="",
        asynch=True,
    )

    proceed = show_confirmation(
        posid=tab_pos_id,
        message="$ALLOWS_RECOVER_TAB_NON_KITCHEN_ORDERS_WILL_BE_DELETED",
        icon="warning",
        timeout=10000,
    )

    close_asynch_dialog(
        posid=pos_id,
        dlgid=wait_dialog_id,
    )
    if proceed is False:
        show_messagebox(
            posid=pos_id,
            message="$OPERATION_CANCELLED_BY_OPERATOR",
            timeout=5000,
        )
        return False

    from actions.login.utils import close_current_order

    closed_order = close_current_order(
        model=get_model(posid=tab_pos_id),
        current_pos_id=str(pos_id),
        pos_to_close_order=str(tab_pos_id),
    )
    if not closed_order:
        return False

    return True


def _get_table_pos_id(
    table_service_dto,  # type: TableServiceDto
):
    # type: (...) -> Optional[int]

    if table_service_dto.table.pos_id is not None:
        return table_service_dto.table.pos_id

    for order in table_service_dto.service_orders:
        if order.state == OrderState.IN_PROGRESS:
            return order.order_xml.get("posId")

    return None


def _open_tab_in_full_mode(
    pos_id,             # type: int
    tab_service_dto,    # type: TableServiceDto
):
    # type: (...) -> None

    from actions.table import reopen_table
    from table_actions import select_table

    if tab_service_dto.status == TableStatus.Totaled:
        reopen_table(
            pos_id=pos_id,
            table_id=tab_service_dto.table.table_id,
        )

        return

    select_table(
        pos_id=pos_id,
        table_id=tab_service_dto.table.table_id,
    )


def _open_tab_in_order_taker_mode(
    pos_id,             # type: int
    tab_service_dto,    # type: TableServiceDto
):
    # type: (...) -> bool

    _open_tab_in_full_mode(
        pos_id=pos_id,
        tab_service_dto=tab_service_dto,
    )

    create_table_order(
        pos_id=pos_id,
        table_id=tab_service_dto.table.table_id,
    )
    return True


def _open_tab_in_cashier_mode(
    pos_id,             # type: int
    table_service,      # type: TableService
    tab_service_dto,    # type: TableServiceDto
):
    # 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_service_dto.table.table_id,
    )
    if not success:
        return False

    if tab_service_dto.status == TableStatus.Totaled:
        return True

    if not _has_sent_kitchen_orders(tab_service_dto=tab_service_dto):
        _create_order_in_pos_cs(
            pos_id=pos_id,
            table_service=table_service,
            tab_service_dto=tab_service_dto,
        )
        return True

    try:
        do_total_table(
            pos_id=pos_id,
            table_id=tab_service_dto.table.table_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_service_dto.table.table_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_service_dto.table.table_id,
                )

        deselect_table(pos_id=pos_id)

    return False


def _has_sent_kitchen_orders(
    tab_service_dto,    # type: TableServiceDto
):
    # type: (...) -> bool

    for order in tab_service_dto.service_orders:
        if order.state == OrderState.STORED:
            state_history = [state.state for state in order.state_history]
            if OrderState.TOTALED not in state_history:
                return True

    return False
