import datetime
from xml.etree import cElementTree as eTree

from model.config import Config, PosMap
from model.error_type import AuthErrorsType, PosValidationErrorsType, OperationErrorsType
from model.exception import AuthValidationErrors, ValidationError, OperationErrors
from model.repository import PosRepositoryModel
from typing import List
from posot import OrderTaker

global api_key_reference


def check_if_business_day_is_opened(repository, model):
    # type: (PosRepositoryModel, eTree) -> None

    is_day_opened = repository.is_day_opened(model)
    if not is_day_opened:
        raise ValidationError(PosValidationErrorsType.BusinessDayNotOpened)


def check_if_operator_is_logged(repository, model):
    # type: (PosRepositoryModel, eTree) -> None

    is_operator_logged = repository.is_operator_logged(model)
    if not is_operator_logged:
        raise ValidationError(PosValidationErrorsType.OperatorIsNotLogged)


def check_if_order_is_in_progress(repository, model):
    # type: (PosRepositoryModel, eTree) -> None

    is_order_in_progress = repository.is_order_in_progress(model)
    if not is_order_in_progress:
        raise ValidationError(PosValidationErrorsType.OrderIsNotInProgress)


def check_if_order_is_totaled(repository, model):
    # type: (PosRepositoryModel, eTree) -> None

    is_order_totaled = repository.is_order_totaled(model)
    if not is_order_totaled:
        raise ValidationError(PosValidationErrorsType.OrderIsNotTotaled)


def check_if_order_is_in_progress_or_totaled(
        repository,     # type: PosRepositoryModel
        model           # type: eTree
):
    # type: (...) -> None

    is_order_in_progress = repository.is_order_in_progress(model)
    is_order_totaled = repository.is_order_totaled(model)

    if not (is_order_in_progress or is_order_totaled):
        raise ValidationError(PosValidationErrorsType.OrderIsNotInProgressOrTotaled)


def check_if_pos_id_is_valid(repository, pos_id):
    # type: (PosRepositoryModel, int) -> None

    pos_list = repository.get_pos_list()
    if not int(pos_id) in pos_list:
        raise ValidationError(PosValidationErrorsType.PosIdNotFound)


def check_if_payment_amount_is_valid(payment_value, model):
    # type: (float, eTree) -> None

    due_amount = float(model.find("CurrentOrder/Order").get("dueAmount"))
    if (payment_value <= 0) or (payment_value > due_amount):
        raise ValidationError(PosValidationErrorsType.PaymentAmountInvalid)


def check_if_payment_type_is_valid(allowed_methods, payment_type):
    # type: (list, int) -> None

    if payment_type not in allowed_methods:
        raise ValidationError(PosValidationErrorsType.PaymentTypeInvalid)


def check_if_due_amount_is_zero(model):
    # type: (eTree) -> None

    due_amount = float(model.find("CurrentOrder/Order").get("dueAmount"))
    if due_amount != 0:
        raise ValidationError(PosValidationErrorsType.DueAmountInvalid)


def check_if_operator_is_loggedin_and_logout(repository, operator, model, pos_id, pos_info, pos_ot):
    if operator is not None and operator.get("state") == "LOGGEDIN":
        if repository.has_current_order(model):
            repository.void_order(pos_id, pos_ot)

        operator_id = operator.get("id")
        if int(operator_id) != pos_info.user_id:
            logouted = repository.logout_delivery_user(pos_id, operator_id)
            if not logouted:
                raise OperationErrors(OperationErrorsType.OperatorLogoutError)
            operator = None

    return operator


def check_current_date_and_model_state_and_fix(repository, operator, model, pos_id, current_date):
    if model.find("PosState").get("period") != current_date or model.find("PosState").get("state") != "OPENED":

        if operator is not None and operator.get("state") == "LOGGEDIN":
            operator_id = operator.get("id")
            logouted = repository.logout_delivery_user(pos_id, operator_id)
            if not logouted:
                raise OperationErrors(OperationErrorsType.OperatorLogoutError)
            operator = None

        if model.find("PosState").get("state") == "OPENED" or model.find("PosState").get("state") == "BLOCKED":
            closed = repository.close_business_period(pos_id)
            if not closed:
                raise OperationErrors(OperationErrorsType.BusynessDayCloseError)

        opened = repository.open_business_period(pos_id)
        if not opened:
            raise OperationErrors(OperationErrorsType.BusynessDayOpenError)

    return operator


def check_if_operator_is_loggouted_and_login(repository, operator, pos_id, pos_info):
    if operator is None or operator.get("state") != "LOGGEDIN":
        authenticate_msg = repository.authenticate_user(str(pos_info.user_id), str(pos_info.user_password))

        if not authenticate_msg:
            raise OperationErrors(OperationErrorsType.OperatorLoginError)

        level, username, long_user_name = authenticate_msg.split('\x00')[:3]

        if operator is None or (operator.get("state") != "OPENED" and operator.get("state") != "PAUSED"):
            opened = repository.open_user(pos_id, str(pos_info.user_id), long_user_name)
            if not opened:
                raise OperationErrors(OperationErrorsType.BusynessDayOpenError)

        logged = repository.login_user(pos_id, str(pos_info.user_id), long_user_name)
        if not logged:
            raise OperationErrors(OperationErrorsType.OperatorLoginError)


def check_business_period(repository, pos_id, config, pos_ot):
    # type: (PosRepositoryModel, int, Config, OrderTaker) -> None

    model = repository.get_model(pos_id)
    current_date = datetime.datetime.now().date().strftime("%Y%m%d")
    operator = model.find("Operator")
    pos_info = next((x for x in config.api_key_mapping if x.pos_id == int(pos_id)), None)  # type: PosMap

    operator = check_if_operator_is_loggedin_and_logout(repository, operator, model, pos_id, pos_info, pos_ot)
    model = repository.get_model(pos_id)

    operator = check_current_date_and_model_state_and_fix(repository, operator, model, pos_id, current_date)

    check_if_operator_is_loggouted_and_login(repository, operator, pos_id, pos_info)


def validate_api_key(request, secure_api_key_list):
    api_key_accepted = False
    pos_id_accepted = False
    if hasattr(request, "pos_id"):
        received_pos_id = int(request.pos_id)
    else:
        received_pos_id = None
    received_api_key = request.system_api_key
    real_pos_id = ""

    for key in secure_api_key_list:
        if received_pos_id is not None:
            if key.api_key == received_api_key:
                api_key_accepted = True
                if received_pos_id == key.abstract_pos_id:
                    pos_id_accepted = True
                    real_pos_id = int(key.pos_id)
        elif key.api_key == received_api_key:
            api_key_accepted = True

    if not api_key_accepted:
        raise AuthValidationErrors(AuthErrorsType.InvalidApiKey)

    if received_pos_id and not pos_id_accepted:
        raise ValidationError(PosValidationErrorsType.PosIdNotFound)

    return str(real_pos_id)


def get_pos_id_from_pos_mapper(abstracted_pos_id, api_key_mapping):
    # type: (str, List[PosMap]) -> str

    for pos_map in api_key_mapping:
        if pos_map.abstract_pos_id == abstracted_pos_id:
            return pos_map.pos_id


def get_abstracted_pos_id_from_pos_mapper(pos_id, api_key_mapping):
    # type: (str, List[PosMap]) -> str

    for pos_map in api_key_mapping:
        if pos_map.pos_id == pos_id:
            return pos_map.abstract_pos_id
