# -*- coding: utf-8 -*-
from xml.etree import cElementTree as eTree

import bustoken
import sysactions
from msgbus import TK_SYS_ACK, FM_PARAM
from mw_helper import show_message_dialog, show_numpad_dialog
from typing import Optional, Union

from .get_operator_level import get_user_level
from ..models import UserLevels

ERROR_MESSAGE = "$ERROR"
MAX_USER_ID_SIZE = 10


def get_authorization(
        pos_id,             # type: Union[int, str]
        min_level=None,     # type: Optional[int]
        model=None,         # type: Optional[eTree.Element]
        timeout=60000,      # type: Optional[int]
        display_title=None, # type: Optional[str]
        user_id=None,       # type: Optional[str]
):
    # type: (...) -> (bool, str)

    auth_type = sysactions.get_cfg(pos_id).key_value("authorizationType", "login").lower()

    ret_user_id = None
    success = False
    try:
        if auth_type == "fingerprint":
            success, ret_user_id = _login_by_fingerprint(
                pos_id=pos_id,
                min_level=min_level,
                user_id=user_id,
                timeout=timeout,
            )

            if success:
                return success, ret_user_id
            else:
                auth_type = "login"

        if auth_type == "login":
            success, ret_user_id = _login_by_numpad(
                pos_id=pos_id,
                min_level=min_level,
                model=model,
                display_title=display_title,
                user_id=user_id
            )
            if success:
                return success, ret_user_id

    except sysactions.AuthenticationFailed as ex:
        if str(ex) == "ACCESS_DENIED":
            show_message_dialog(pos_id, message="$ACCESS_DENIED")
        elif str(ex) == "ERROR_READING_FINGERPRINT":
            show_message_dialog(pos_id, message="ERROR_READING_FINGERPRINT")
        else:
            show_message_dialog(pos_id, message="$INVALID_USERNAME_OR_PASSWORD")

    finally:
        if success:
            sysactions.set_custom(pos_id, 'Last Manager ID', ret_user_id)
            sysactions.set_custom(pos_id, 'Authorization Level', get_user_level(ret_user_id))

    return False, None


def _login_by_fingerprint(
        pos_id,             # type: int
        min_level=None,     # type: Optional[int]
        user_id=None,       # type: Optional[str]
        timeout=60000,      # type: Optional[int]
):
    # type: (...) -> (bool, str)

    try:
        ret = sysactions.mbcontext.MB_EasySendMessage("FingerPrintReader{0}".format(pos_id), bustoken.TK_FPR_OK)
        if ret.token == TK_SYS_ACK:
            ret = sysactions.mbcontext.MB_EasySendMessage(
                "FingerPrintReader{0}".format(pos_id),
                bustoken.TK_FPR_AUTHORIZE_USER,
                format=FM_PARAM,
                data=pos_id,
                timeout=timeout,
            )

        if ret.token == bustoken.TK_FPR_TIMEOUT:
            return None, None
    except Exception as _:  # noqa
        return None, None

    if ret.token != TK_SYS_ACK:
        raise sysactions.AuthenticationFailed("ERROR_READING_FINGERPRINT")
    elif ret.data == "":
        raise sysactions.AuthenticationFailed("INVALID_USER_OR_PASSWORD")
    else:
        user_id = user_id or ret.data
        try:
            user_xml_str = sysactions.get_user_information(user_id)
        except Exception as _:  # noqa
            raise sysactions.AuthenticationFailed("ERROR_READING_FINGERPRINT")

        if user_xml_str is None:
            raise sysactions.AuthenticationFailed("ERROR_READING_FINGERPRINT")

    user_xml = eTree.XML(user_xml_str)
    user_element = user_xml.find("user")
    user_level = int(user_element.attrib["Level"])
    if user_level < min_level:
        raise sysactions.AuthenticationFailed("ACCESS_DENIED")

    return True, user_id


def _login_by_numpad(
        pos_id,             # type: int
        min_level=None,     # type: Optional[int]
        model=None,         # type: Optional[eTree.Element]
        display_title=None, # type: Optional[str]
        user_id=None,       # type: Optional[str]
):
    # type: (...) -> (bool, str)

    if display_title is None:
        display_title = _get_default_numpad_login_title(min_level, model, pos_id)

    if not user_id:
        user_id = show_numpad_dialog(pos_id, display_title, "$ENTER_USER_ID", mask='LOGIN')
        if user_id is None:
            return False, None
        if user_id == '':
            show_message_dialog(pos_id, message="$INVALID_USER")
            return False, None

    login_by_nfc = len(user_id) >= MAX_USER_ID_SIZE
    if login_by_nfc:
        user_info = sysactions.authenticate_user_nfc(nfc_uuid=user_id, min_level=min_level)
        user_id = user_info.get("UserId")
    else:
        password = show_numpad_dialog(pos_id, display_title, "$ENTER_PASSWORD|{}".format(user_id), "password")
        if password is None:
            return False, None

        sysactions.authenticate_user(user_id, password, min_level=min_level)

    sysactions.sys_log_info("Authentication succeeded for POS id: {}, user id: {}".format(pos_id, user_id))
    return True, user_id


def _get_default_numpad_login_title(
        min_level,      # type: int
        model,          # type: eTree.Element
        pos_id,         # type: int
):
    # type: (...) -> str

    model = model or sysactions.get_model(pos_id)
    user_level_name = UserLevels.get_name(min_level)
    if user_level_name is None:
        user_levels = UserLevels.list_values()
        for level in user_levels:
            if min_level <= level:
                user_level_name = UserLevels.get_name(level)
                break

    display_title = "$USER_AUTHENTICATION"
    if user_level_name is not None:
        level_name = sysactions.translate_message(model, user_level_name)
        display_title = "$USER_AUTHENTICATION_WITH_PARAMETER|{}".format(level_name)

    return display_title
