# -*- coding: utf-8 -*-
import datetime
import time

import msgbus
import persistence
import sysactions
from xml.etree import cElementTree as eTree

from actions.models import AuthorizationResponse
from actions.models.mw_sys_errors import MwSysErrors
from actions.util import (
    get_authorization,
    get_operator_info,
    get_user_level,
)
from api.order import get_order_service
from model.customexception import CloseOrderByOperatorCloseException
from typing import (
    Dict,
    Optional,
)
from .. import (
    logger,
    mb_context,
    pos_config,
)


def get_pos_service(pos_id):
    return "POS%d" % int(pos_id)


def login_user(pos_id, user_id):
    try:
        long_name, level, user_id = get_user_info(user_id)

        msg = sysactions.send_message(
            get_pos_service(pos_id),
            msgbus.TK_POS_USERLOGIN,
            msgbus.FM_PARAM, "{}\0{}\0{}".format(pos_id, user_id, long_name),
            timeout=60 * 1000000
        )
        if msg.token != msgbus.TK_SYS_ACK:
            model = sysactions.get_model(pos_id)
            message = sysactions.translate_message(model, "ERROR") + ": "
            error = MwSysErrors.get_name(msg.data.replace("\0", ""))
            _handle_current_order_error(error, model, pos_id)
            message += sysactions.translate_message(model, error)
            sysactions.show_messagebox(pos_id, message, "$INFORMATION")
            logger.error("Invalid response from PosCtrl while login user: {} - {}".format(msg.token, msg.data))
            return False

    except (Exception, BaseException):
        logger.error("Exception while login user")
        sysactions.show_messagebox(pos_id, "$CANNOT_LOGIN_OPERATOR")
        return False

    return True


def _handle_current_order_error(error, model, pos_id):
    # type: (str, eTree.XML, int) -> None

    try:
        if error == "SE_ORDEROPENED":
            order_service = get_order_service(pos_id=pos_id)
            order_service.void_order()
    except (Exception,):
        logger.exception("Error voiding order on user login")


def open_user(pos_id, user_id):
    msg = None
    try:
        long_name, level, user_id = get_user_info(user_id)

        msg = sysactions.send_message(
            get_pos_service(pos_id),
            msgbus.TK_POS_USEROPEN,
            msgbus.FM_PARAM,
            "%s\0%s\0%s\0%s\0%s" % (pos_id, user_id, 0.0, long_name, level),
            timeout=60 * 1000000
        )
        if msg.token != msgbus.TK_SYS_ACK:
            if _user_is_already_opened(msg):
                return True

            logger.exception("Error opening user: Token: {} / Data: {}".format(msg.token, msg.data))
            sysactions.show_messagebox(pos_id, "$CANNOT_OPEN_OPERATOR")
            return False
    except (Exception, BaseException):
        logger.exception("Error opening user {} on POS {}".format(user_id, pos_id))
        message = "$CANNOT_OPEN_OPERATOR"
        has_error = msg is not None and msg.data and len(msg.data.split("\0")) > 1
        if has_error and msg.data.split("\0")[0] in MwSysErrors.list_values():
            model = sysactions.get_model(pos_id)
            message = sysactions.translate_message(model, MwSysErrors.get_name(msg.data.split("\0")[0]))

        sysactions.show_messagebox(pos_id, message)
        return False
    return True


def _user_is_already_opened(msg):
    return msg.data.startswith("5007")


def close_user(
    pos_id,                     # type: str
    user_id,                    # type: str
    pos_to_close_user=None,     # type: Optional[str]
):
    # type: (...) -> bool

    if pos_to_close_user is None:
        pos_to_close_user = pos_id

    try:
        msg = sysactions.send_message(
            get_pos_service(pos_id=pos_to_close_user),
            msgbus.TK_POS_USERLOGOUT,
            msgbus.FM_PARAM,
            "%s\0%s" % (pos_to_close_user, user_id),
            timeout=60 * 1000000
        )
        if msg.token != msgbus.TK_SYS_ACK:
            if _user_is_already_closed(msg):
                return True

            logger.exception("Error closing user: Token: {} / Data: {}".format(msg.token, msg.data))
            sysactions.show_messagebox(pos_id, "$CANNOT_CLOSE_OPERATOR")
            return False
    except (Exception, BaseException):
        logger.exception("Error closing user {} on POS {}".format(user_id, pos_to_close_user))
        sysactions.show_messagebox(pos_id, "$CANNOT_CLOSE_OPERATOR")
        return False

    return True


def _user_is_already_closed(msg):
    return msg.data.startswith("5007")


def check_current_day(pos_id):
    model = sysactions.get_model(pos_id)
    if sysactions.is_day_blocked(model):
        sysactions.show_messagebox(pos_id, "$POS_IS_BLOCKED_BY_TIME", "$INFORMATION")
        return False

    if not sysactions.is_day_opened(model):
        sysactions.show_messagebox(pos_id, "$NEED_TO_OPEN_BDAY_FIRST", "$INFORMATION")
        return False

    return True


def get_user_info(user_id):
    msg = sysactions.send_message("UserControl", msgbus.TK_USERCTRL_GETINFO, msgbus.FM_PARAM, user_id)
    if msg.token != msgbus.TK_SYS_ACK:
        raise Exception("Error getting user information {}-{}".format(msg.token, msg.data))

    user_info = eTree.XML(msg.data)
    long_name = user_info.find("user").get("LongName")
    level = user_info.find("user").get("Level")
    user_id = user_info.find("user").get("UserId")

    return long_name, level, user_id


def print_open_day_report(pos_id, initial_float):
    model = sysactions.get_model(pos_id)
    operator = sysactions.get_current_operator(model)
    operator_name = (operator.get("name")).encode('utf-8')
    operator_id = operator.get("id")
    period = sysactions.get_business_period(model)
    authorized_by = sysactions.get_custom(model, 'Last Manager ID', operator_id)

    sysactions.print_report(
        pos_id,
        model,
        True,
        "login_operator_report",
        pos_id,
        operator_id,
        operator_name,
        initial_float,
        period,
        authorized_by
    )


def get_pod_type_and_pos_function(pos_id):
    pod_type = sysactions.get_cfg(pos_id).key_value("pod") or 'FL'
    pos_function_config = sysactions.get_cfg(pos_id).key_value("posFunction") or 'FL'
    pos_function_list = pos_function_config.split("|")
    pos_function = pos_function_list[0]
    if len(pos_function_list) > 1:
        pos_function_index = sysactions.show_listbox(pos_id, pos_function_list, message="$SELECT_A_POS_FUNCTION")

        if pos_function_index is None:
            return None, None

        pos_function = pos_function_list[pos_function_index]

    return pod_type, pos_function


def change_pod_type_and_pos_function(pos_id, pod_type, pos_function):
    if pos_function == "DS":
        sysactions.check_main_screen(pos_id)
    sysactions.send_message(
        get_pos_service(pos_id),
        msgbus.TK_POS_SETPOD,
        msgbus.FM_PARAM, "{}\0{}\0".format(pos_id, pod_type),
        timeout=60 * 1000000)
    sysactions.send_message(
        get_pos_service(pos_id),
        msgbus.TK_POS_SETFUNCTION,
        msgbus.FM_PARAM, "{}\0{}\0".format(pos_id, pos_function),
        timeout=60 * 1000000)


def get_initial_float(
    pos_id,         # type: str
    pod_type,       # type: str
    pos_function,   # type: str
):
    # type: (...) -> float

    if pod_type != "OT" and pos_function != "OT":
        index = None
        list_min_values_drawer = sysactions.get_storewide_config("Store.MinValuesDrawer")
        if list_min_values_drawer:
            initial_float = _get_initial_float_by_min_values(
                pos_id=pos_id,
                list_min_values_drawer=list_min_values_drawer,
                index=index,
            )
        else:
            initial_float = _get_initial_float_by_user_input(pos_id=pos_id)

        return round(float(initial_float or 0.0), 2)
    else:
        return 0.0


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

    initial_float = sysactions.show_keyboard(
        posid=pos_id,
        message="$ENTER_THE_INITIAL_FLOAT_AMOUNT",
        title="$OPERATOR_OPENING",
        mask="CURRENCY",
        numpad=True,
        timeout=720000,
    )
    if initial_float is None:
        raise sysactions.StopAction()

    return initial_float


def _get_initial_float_by_min_values(
    pos_id,                     # type: str
    list_min_values_drawer,     # type: str
    index,                      # type: Optional[int]
):
    # type: (...) -> str

    list_limits = [("R$%.2f" % float(x)).replace(".", ",") for x in list_min_values_drawer.split(";")]
    if len(list_limits) > 1:
        index = sysactions.show_listbox(
            posid=pos_id,
            options=list_limits,
            message="$ENTER_THE_INITIAL_FLOAT_AMOUNT",
            title="$OPERATOR_LOGIN",
            icon="info",
            timeout=720000,
        )

    elif len(list_limits) == 1:
        index = 0

    if index is None:
        raise sysactions.StopAction()

    initial_float = list_min_values_drawer.split(";")[index]
    return initial_float


def save_initial_float(
    initial_float,  # type: float
    pos_id,         # type: str
    user_id,        # type: str
):
    # type: (...) -> None

    try:
        wait_user_logged_in(pos_id, user_id)
        model = sysactions.get_model(pos_id)
        _, session = get_operator_info(model, user_id)
        sql = "insert into Transfer (Period, PosId, SessionId, Type, Description, Amount, TenderId) " \
              "values ({}, {}, '{}', 1, 'INITIAL_AMOUNT', {}, 0);"\
            .format(datetime.datetime.now().strftime('%Y%m%d'), pos_id, session, initial_float)
        persistence.Driver().open(mb_context, pos_id).query(sql)

        _send_initial_amount_audit_event(
            pos_id=pos_id,
            model=model,
            session=session,
            initial_float=initial_float,
        )
    except (Exception,):
        logger.exception("Fail set initial float to user: {}".format(user_id))


def _send_initial_amount_audit_event(
    pos_id,         # type: str
    model,          # type: eTree.Element
    session,        # type: str
    initial_float,  # type: float
):
    # type: (...) -> None

    timestamp = time.gmtime(time.time())
    timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", timestamp)
    period = sysactions.get_business_period(model=model)

    initial_amount_xml = eTree.Element(
        "InitialAmount",
        storeId=pos_config.store_id,
        period=period,
        posId=pos_id,
        sessionId=session,
        amount=str(initial_float),
        tenderId="0",
        timestamp=timestamp,
    )

    mb_context.MB_EasyEvtSend(
        subject="INITIAL_AMOUNT",
        type="INITIAL_AMOUNT",
        xml=eTree.tostring(initial_amount_xml),
    )


def wait_user_logged_in(pos_id, user_id):
    # type: (str, str) -> None

    time_limit = (time.time() + 1.5)
    while (time.time() < time_limit) and (not has_session(sysactions.get_model(pos_id), user_id)):
        time.sleep(0.1)


def has_session(model, user_id):
    # type: (eTree, str) -> bool

    _, session = get_operator_info(model, user_id)
    return session not in ['', None]


def user_inserted_initial_amount(pos_id, session):
    try:
        if session in ['', None]:
            return False

        sql = "select count(*) from Transfer " \
              "where SessionId = '{}' and Description = 'INITIAL_AMOUNT'".\
            format(session)
        conn = persistence.Driver().open(mb_context, pos_id)
        row = conn.select(sql).get_row(0)
        return int(row.get_entry(0)) > 0
    except (Exception,):
        return False


def get_authorization_if_necessary(
    model,      # type: eTree.XML
    pos_id,     # type: str
    user_info,  # type: Dict
    min_level   # type: int
):
    # type: (...) -> AuthorizationResponse

    user_id = user_info["UserId"]
    user_name = user_info["UserName"]
    user_level = int(user_info["Level"] or 0)
    if user_level < min_level:
        response = get_authorization(pos_id, min_level=min_level, model=model)
        if not response[0]:
            return AuthorizationResponse(False, "", "")

        user_name = response[1]

    return AuthorizationResponse(
        success=True,
        authorized_by=user_name,
        authorized_by_level=get_user_level(user_id=user_id),
    )


def save_last_authorization(
    pos_id,                 # type: str
    authorized_by,          # type: str
    authorized_by_level,    # type: str
):
    # type: (...) -> None

    sysactions.set_custom(pos_id, 'Last Manager ID', authorized_by, True)
    sysactions.set_custom(pos_id, 'Authorization Level', authorized_by_level, True)


def validate_user_level(
    pos_id,     # type: str
    user_info,  # type: Dict
):
    # type: (...) -> None

    if int(user_info.get("Level", -1)) < 0:
        logger.error("Access Denied for user: {}".format(user_info["UserName"]))
        sysactions.show_messagebox(pos_id, "$ACCESS_DENIED", "$INFORMATION")
        raise sysactions.StopAction()


def request_operator_confirmation(
    pos_id,             # type: str
    pos_to_close_user,  # type: str
):
    # type: (...) -> None

    wait_dialog_id = sysactions.show_messagebox(
        posid=pos_id,
        message="$WAITING_FOR_OPERATOR_CONFIRMATION",
        buttons="",
        asynch=True,
    )
    confirmation = sysactions.show_confirmation(
        posid=pos_to_close_user,
        message="$CONFIRM_CLOSE_USER_FROM_ANOTHER_POS",
        icon="warning",
        timeout=10000,
    )
    sysactions.close_asynch_dialog(
        posid=pos_id,
        dlgid=wait_dialog_id,
    )
    if confirmation is False:
        sysactions.show_messagebox(
            posid=pos_id,
            message="$OPERATION_CANCELLED_BY_OPERATOR",
            timeout=5000,
        )
        raise sysactions.StopAction()


def close_current_order(
    model,                  # type: eTree.Element
    current_pos_id,         # type: str
    pos_to_close_order,     # type: str
    user_id=None,           # type: Optional[str]
):
    # type: (...) -> bool

    logger.info("[Close order from another POS] Init process can_close_order_from_another_pos")
    current_order = sysactions.get_current_order(model=model)
    if current_order is not None:
        from actions.login.common_close_user import (
            close_quick_service_order_from_another_pos,
            close_table_service_from_another_pos,
        )

        is_quick_service_order = _order_is_quick_service(order=current_order)
        if pos_to_close_order is not None:
            sysactions.show_messagebox(
                posid=pos_to_close_order,
                message="$PLEASE_WAIT",
                timeout="$INFO",
                asynch=True,
                buttons=""
            )

        try:
            if is_quick_service_order:
                close_quick_service_order_from_another_pos(
                    pos_id=current_pos_id,
                    order=current_order,
                    pos_to_close_user=pos_to_close_order,
                    user_id=user_id,
                    must_store_quick_service_order=True,
                )
            else:
                close_table_service_from_another_pos(
                    pos_id=current_pos_id,
                    order=current_order,
                    pos_to_close_user=pos_to_close_order,
                    user_id=user_id,
                    ask_to_void_order=False,
                )
        except (CloseOrderByOperatorCloseException, Exception):
            logger.exception("[Close order from another POS] Cannot complete Close Order")
            return False
        finally:
            sysactions.close_asynch_dialog(pos_to_close_order, -1)

    logger.info("[Close order from another POS] End process can_close_order_from_another_pos")

    return True


def _order_is_quick_service(
    order,  # type: eTree.Element
):
    # type: (...) -> bool

    table_id = order.find("CustomOrderProperties/OrderProperty/[@key='TABLE_ID']")
    return table_id is None
