# -*- coding: utf-8 -*-
import inspect

from xml.etree import cElementTree as eTree
from functools import wraps
from datetime import datetime

from posot import OrderTaker
from sysactions import (
    StopAction,
    UiOutOfSync,
    get_current_order,
    get_line_product_name,
    get_model,
    get_operator_session,
    get_podtype,
    get_posot,
    show_messagebox,
)
from systools import sys_log_exception

from actions import logger
from actions.config import get_function_authorization_level_from_config
from actions.util import (
    get_user_level,
    get_authorization,
)
from actions.order import (
    must_sync_ui,
    sync_ui_and_model,
)
from actions.pos import pos_need_to_be_blocked
from api.order import get_order_service

from typing import (
    Any,
    Dict,
    List,
    Optional,
)

configurations = None


def initialize_manager_authorizations(config):
    global configurations
    configurations = config


def user_authorized_to_execute_action(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        user_id = None
        pos_id = _get_pos_id(args, kwargs)
        if not pos_id:
            return False

        try:
            function_level = get_function_authorization_level_from_config(
                config=configurations,
                function_name=f.func_name,
            )
            model = get_model(posid=pos_id)
            operator = model.find("./Operator")
            if operator is not None:
                user_id = operator.get("id")
                if get_user_level(user_id=user_id) <= function_level:
                    status, user_id = get_authorization(
                        pos_id=pos_id,
                        min_level=function_level,
                        model=model,
                    )
                    if not status:
                        return StopAction()

                if function_level > 0:
                    _save_authentication_info_on_order(
                        executed_function=f.func_name,
                        model=model,
                        pos_id=pos_id,
                        user_id=user_id,
                    )

        except (Exception,) as ex:
            error_message = "$GET_USER_AUTHORIZATION_ERROR_" + str(type(ex).__name__).upper()
            sys_log_exception("Error validating user authorization")
            show_messagebox(pos_id, message=error_message, icon="error")
            return False

        if 'auth_data' in inspect.getargspec(f).args:
            kwargs['auth_data'] = user_id

        return f(*args, **kwargs)

    return decorated_function


def block_action_if_pos_is_blocked(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        pos_id = _get_pos_id(args, kwargs)
        if not pos_id:
            return False

        try:
            model = get_model(pos_id)
            pod_type = get_podtype(model)
            if pod_type != 'TT' and pos_need_to_be_blocked(pos_id):
                show_messagebox(pos_id, message="$POS_BLOCKED", icon="error")
                raise StopAction()
        except (StopAction,):
            raise
        except (Exception,):
            return False

        return f(*args, **kwargs)

    return decorated_function


def block_action_if_there_is_no_order(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        pos_id = _get_pos_id(args, kwargs)
        if not pos_id:
            return False

        if not get_order_service(pos_id=pos_id).has_current_order():
            show_messagebox(pos_id, "$HAS_NO_ITEMS_IN_ORDER", title="")
            return False

        return f(*args, **kwargs)

    return decorated_function


def block_action_if_order_items_not_resolved(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        from posactions import list_open_options

        pos_id = _get_pos_id(args, kwargs)
        if not pos_id:
            return False

        order = get_order_service(pos_id=pos_id).get_order_picture()

        sale_lines = order.findall("SaleLine")
        active_sale_lines = _get_active_order_lines(sale_lines)
        is_remote_order = order.find("CustomOrderProperties/OrderProperty[@key='REMOTE_ORDER_ID']")
        if not active_sale_lines and is_remote_order is None:
            show_messagebox(pos_id, "$HAS_NO_ITEMS_IN_ORDER", title="")
            return False

        option = list_open_options(order)
        if option is not None:
            model = get_model(pos_id)
            prod_name = get_line_product_name(model, int(option.get("lineNumber")))
            show_messagebox(pos_id, message="$NEED_TO_RESOLVE_OPTION|%s" % (prod_name.encode("UTF-8")), icon="error")
            return False

        return f(*args, **kwargs)

    return decorated_function


def validate_order_state(
    f,  # type: Any
):
    # type: (...) -> Any[Any, bool]

    @wraps(f)
    def decorated_function(
        *args,      # type: str
        **kwargs    # type: str
    ):
        # type: (...) -> Any[Any, bool]

        pos_id = _get_pos_id(
            args=args,
            kwargs=kwargs,
        )
        if not pos_id:
            return False

        try:
            model = get_model(posid=pos_id)
            order_taker = get_posot(model=model)
            order_id = model.find(".//Order").get("orderId")
            fix_current_order_in_model(
                pos_id=pos_id,
                model=model,
                order_taker=order_taker,
                order_id=order_id,
            )
        except (StopAction, UiOutOfSync):
            raise
        except (Exception,):
            return False

        return f(
            *args,
            **kwargs
        )

    return decorated_function


def fix_current_order_in_model(
    pos_id,         # type: str
    model,          # type: eTree.Element
    order_taker,    # type: OrderTaker
    order_id,       # type: str
):
    # type: (...) -> Optional[bool]

    logger.info("Trying to fix POS {}. Current Order Id: {}".format(
        pos_id,
        order_id,
    ))
    model_order_picture = get_current_order(model=model)
    current_order_picture = eTree.XML(order_taker.orderPicture(orderid=order_id))

    if model_order_picture.get("state") != current_order_picture.find("Order").get("state"):
        sync_ui_and_model(pos_id=pos_id)
    else:
        must_sync_ui(
            pos_id=pos_id,
            different_than="IN_PROGRESS",
        )

    return False


def user_must_be_logged(
    f,  # type: Any
):
    # type: (...) -> Any[Any, bool]

    @wraps(f)
    def decorated_function(
        *args,      # type: List[Any]
        **kwargs    # type: Dict[str, Any]
    ):
        # type: (...) -> Any[Any, bool]

        pos_id = _get_pos_id(
            args=args,
            kwargs=kwargs,
        )
        if not pos_id:
            return False

        try:
            model = get_model(posid=pos_id)
            session_id = get_operator_session(model=model)
            if session_id in ("", None, "None"):
                message = "$USER_MUST_BE_LOGGED_TO_DO_THE_REQUIRED_ACTION_AND_UI_WILL_BE_REFRESH"
                raise UiOutOfSync(
                    pos_id=pos_id,
                    custom_message=message,
                )
        except (StopAction, UiOutOfSync):
            raise
        except (Exception,):
            return False

        return f(
            *args,
            **kwargs
        )

    return decorated_function


def _get_pos_id(
    args,       # type: List[Any]
    kwargs,     # type: Dict[str, Any]
):
    pos_id = None
    if len(args) > 0:
        pos_id = args[0]
    elif "pos_id" in kwargs:
        pos_id = kwargs["pos_id"]
    elif "posid" in kwargs:
        pos_id = kwargs["posid"]
    else:
        sys_log_exception(message="PosId not found to authorize")

    return pos_id


def _get_order_deleted_lines(
    sale_lines,     # type: List[eTree.Element]
):
    # type: (...) -> List[str]

    return map(
        lambda x: x.get("lineNumber"),
        filter(lambda x: x.get("level") == "0" and x.get("qty") == "0", sale_lines),
    )


def _get_active_order_lines(
    sale_lines,     # type: List[eTree.Element]
):
    # type: (...) -> List[eTree.Element]

    deleted_line_numbers = _get_order_deleted_lines(sale_lines=sale_lines)
    return filter(lambda x: x.get("lineNumber") not in deleted_line_numbers, sale_lines)


def _save_authentication_info_on_order(
    executed_function,      # type: str
    model,                  # type: eTree.Element
    pos_id,                 # type: int
    user_id,                # type: str
):
    # type: (...) -> None

    if executed_function is None:
        return

    if not model:
        model = get_model(posid=pos_id)

    pos_ot = get_posot(model=model)
    order = model.find("CurrentOrder/Order")
    order_id = order.get("orderId") if order else None

    if order_id:
        property_name = "{}: {}".format(executed_function, datetime.now().strftime("%m/%d/%Y %H:%M:%S"))
        pos_ot.setOrderCustomProperty(
            key=property_name,
            value=user_id,
            orderid=order_id,
        )
