from datetime import datetime
import json
from typing import (
    Any,
    Dict,
    List,
    Optional,
)
from xml.etree import cElementTree as eTree

from sysactions import (
    action,
    get_user_information,
    show_any_dialog,
    show_confirmation,
    show_keyboard,
    show_messagebox,
    StopAction,
)

from mw_helper import show_editable_list_box_dialog, show_numpad_dialog

from actions.models import UserLevels
from actions.products import getProducts
from application.interactor import DailyGoalsInteractor
from model.customexception import (
    ComponentNotFound,
    DateFormatException,
    ErrorGettingSalesChallenge,
)
from manager.reports import sale_challenge_report


DATE_FORMAT = "%Y-%m-%d"


@action
def show_sales_challenges(
    pos_id,         # type: int
    operator_id,    # type: str
):
    # type: (...) -> None

    operator_level = eTree.XML(get_user_information(user_number=operator_id)).find("user").get("Level")
    is_manager = int(operator_level) >= UserLevels.MANAGER.value
    options_header = "$DESCRIPTION#$BEGIN_DATE#$END_DATE#$STATUS#$ACTIONS"
    options, sale_challenges = _get_sales_challenges(pos_id)
    options = options_header + "|" + "|".join(options)
    body = dict(isManager=is_manager, rows=options)
    response = show_editable_list_box_dialog(
        pos_id=pos_id,
        options=json.dumps(body).encode("utf-8"),
        title="$CHALLENGES",
        buttons="$CREATE_NEW|$CANCEL",
    )
    if not response:
        return
    action_name = response.split(',')[-1]
    if action_name == 'CREATE_NEW':
        _create_sale_challenge(
            pos_id=pos_id,
            operator_id=operator_id,
        )
    else:
        action_name, option_line = action_name.split(':')

        sale_challenge_id = sale_challenges[int(option_line) - 1]["challenge_id"]
        if action_name == 'EDIT':
            _edit_sale_challenge(
                pos_id=pos_id,
                sale_challenge=sale_challenges[int(option_line) - 1],
                operator_id=operator_id,
                editable=is_manager,
            )
        elif action_name == 'DELETE':
            _delete_sale_challenge(
                pos_id=pos_id,
                sale_challenge_id=sale_challenge_id,
                operator_id=operator_id,
            )
        elif action_name == 'REPORT':
            sale_challenge_report(
                pos_id=pos_id,
                sale_challenge_id=sale_challenge_id,
                is_manager=is_manager,
                operator_id=operator_id,
            )


def _display_and_validate_sale_challenge(pos_id, sale_challenge, can_edit=True, is_update=False):
    # type: (int, Dict, bool, bool) -> Any[Dict, None]

    sale_challenge["canEdit"] = can_edit
    response = show_any_dialog(pos_id, "editSaleChallengeDialog", "", "", json.dumps(sale_challenge), "", "", 600000,
                               "", "", "", "", "", False)

    sale_challenge_resp = json.loads(response)

    if not can_edit or not sale_challenge_resp:
        return None

    if is_update:
        is_ok, message = _validate_sale_challenge(sale_challenge_resp, sale_challenge)
    else:
        is_ok, message = _validate_sale_challenge(sale_challenge_resp)

    if not is_ok:
        show_messagebox(pos_id, message, "$ERROR")
        new_sale_challenge = dict(name=sale_challenge_resp["NAME"], startDate=sale_challenge_resp["START_DATE"],
                                  endDate=sale_challenge_resp["END_DATE"], products=sale_challenge_resp["PRODUCTS"])
        return _display_and_validate_sale_challenge(pos_id, new_sale_challenge, can_edit, is_update)
    return sale_challenge_resp


def _create_sale_challenge(
    pos_id,         # type: int
    operator_id,    # type: str
):
    # type: (...) -> None

    sale_challenge = dict(name="", startDate="", endDate="", products=[])

    sale_challenge_resp = _display_and_validate_sale_challenge(pos_id, sale_challenge)
    if sale_challenge_resp is None:
        return

    if _insert_sale_challenge(
        pos_id=pos_id,
        sale_challenge=sale_challenge_resp,
        operator_id=operator_id,
    ):
        show_messagebox(pos_id, "$SALE_CHALLENGE_CREATION_SUCCESS", "$INFORMATION")


def _edit_sale_challenge(
    pos_id,             # type: int
    sale_challenge,     # type: Dict
    operator_id,        # type: str
    editable=False,     # type: bool
):
    # type: (...) -> None

    all_products = json.loads(getProducts(pos_id=pos_id))
    products = json.loads(sale_challenge["products"])
    products = [str(p) for p in products if str(p) in all_products]
    sale_challenge_body = dict(name=sale_challenge["name"], startDate=sale_challenge["start_date"],
                               endDate=sale_challenge["end_date"], products=products)

    today = datetime.today().date()
    end_date = datetime.strptime(sale_challenge["end_date"], DATE_FORMAT).date()
    if today > end_date or not editable:
        sale_challenge_resp = _display_and_validate_sale_challenge(pos_id, sale_challenge_body, can_edit=False)
    else:
        sale_challenge_resp = _display_and_validate_sale_challenge(pos_id, sale_challenge_body, is_update=True)
    if sale_challenge_resp is None:
        return

    if _update_sale_challenge(
        sale_challenge=sale_challenge_resp,
        sale_challenge_id=sale_challenge["challenge_id"],
        operator_id=operator_id,
    ):
        show_messagebox(pos_id, "$SALE_CHALLENGE_EDITION_SUCCESS", "$INFORMATION")


def _delete_sale_challenge(
    pos_id,               # type: int
    sale_challenge_id,    # type: int
    operator_id,          # type: str
):
    # type: (...) -> None

    confirm = show_confirmation(pos_id, message="$DELETE_SALE_CHALLENGE_CONFIRMATION", buttons="$YES|$NO")
    if confirm:
        DailyGoalsInteractor.remove_sales_challenge(sale_challenge_id)
    else:
        show_sales_challenges(
            pos_id=pos_id,
            operator_id=operator_id,
        )


def _get_sales_challenges(pos_id):
    # type: (int) -> (List[str], List[Dict])

    try:
        options = []
        sales_challenges = DailyGoalsInteractor.get_sales_challenge(pos_id, 5)

        for challenge in sales_challenges:
            line = u"{}#{}#{}#{}".format(
                challenge["name"],
                challenge["start_date"],
                challenge["end_date"],
                challenge["challenge_id"],
            )
            options.append(line)

        return options, sales_challenges

    except (ComponentNotFound, ErrorGettingSalesChallenge) as ex:
        show_messagebox(
            posid=pos_id,
            message=ex.message,
            title="$ERROR",
            icon="error",
        )
        raise StopAction()


def _build_body_sale_challenge(
    sale_challenge,             # type: Dict
    operator_id,                # type: str
    sale_challenge_id=None,     # type: Optional[int]
):
    # type: (...) -> Dict

    user_id = int(operator_id)
    products = [int(p) for p in sale_challenge["PRODUCTS"]]
    return dict(challenge_id=sale_challenge_id, name=sale_challenge["NAME"],
                start_date=sale_challenge["START_DATE"], end_date=sale_challenge["END_DATE"],
                products=json.dumps(products), created_by=user_id)


def _insert_sale_challenge(
    pos_id,             # type: int
    sale_challenge,     # type: Dict
    operator_id,        # type: Optional[str]
):
    # type: (...) -> bool

    formatted_sale_challenge = _build_body_sale_challenge(
        sale_challenge=sale_challenge,
        operator_id=operator_id,
    )
    has_active_sale_challenges = DailyGoalsInteractor.get_sales_challenge(
        pos_id=pos_id,
        quantity=1,
    )
    if not has_active_sale_challenges:
        return DailyGoalsInteractor.insert_sales_challenge(sale_challenge=formatted_sale_challenge)
    else:
        show_messagebox(
            posid=pos_id,
            message="$ALREADY_ACTIVE_SALE_CHALLENGE",
        )


def _update_sale_challenge(
    sale_challenge,     # type: Dict
    sale_challenge_id,  # type: int
    operator_id,        # type: str
):
    # type: (...) -> bool

    formatted_sale_challenge = _build_body_sale_challenge(
        sale_challenge=sale_challenge,
        operator_id=operator_id,
        sale_challenge_id=sale_challenge_id,
    )
    return DailyGoalsInteractor.update_sales_challenge(formatted_sale_challenge)


@action
def show_challenge_keyboard_dialog(pos_id, message='', last_value=''):
    response = show_keyboard(pos_id, message, defvalue=last_value, title="", numpad=False)
    return response if response else last_value


@action
def show_challenge_numpad_dialog(pos_id, title='', last_value=''):
    return show_numpad_dialog(
        pos_id=pos_id,
        title=title,
        message="",
        mask="DATE",
        default_value=last_value,
    )


def _validate_sale_challenge(sale_challenge, original_sale_challenge=None):
    # type: (Dict, Dict) -> (bool, str)

    if "NAME" in sale_challenge and len(sale_challenge["NAME"]) == 0:
        return False, "$SALE_CHALLENGE_WITHOUT_NAME"
    if "PRODUCTS" in sale_challenge and len(sale_challenge["PRODUCTS"]) == 0:
        return False, "$SALE_CHALLENGE_WITHOUT_PRODUCTS"
    if "START_DATE" in sale_challenge and len(sale_challenge["START_DATE"]) == 0:
        return False, "$SALE_CHALLENGE_WITHOUT_START_DATE"
    if "END_DATE" in sale_challenge and len(sale_challenge["END_DATE"]) == 0:
        return False, "$SALE_CHALLENGE_WITHOUT_END_DATE"
    try:
        today = datetime.today().date()
        start_date = datetime.strptime(sale_challenge["START_DATE"], DATE_FORMAT).date()
        end_date = datetime.strptime(sale_challenge["END_DATE"], DATE_FORMAT).date()
        if today > start_date:
            return False, "$SALE_CHALLENGE_START_DATE_CANNOT_BE_EARLIER_THAN_TODAY"
        if today > end_date:
            return False, "$SALE_CHALLENGE_END_DATE_CANNOT_BE_EARLIER_THAN_TODAY"
        if start_date > end_date:
            return False, "$SALE_CHALLENGE_START_DATE_AFTER_THAN_END_DATE"
    except (DateFormatException,):
        return False, "$INVALID_DATE_FORMAT"

    if original_sale_challenge:
        if sale_challenge["NAME"] != original_sale_challenge["name"] or \
                sale_challenge["START_DATE"] != original_sale_challenge["startDate"] or \
                sale_challenge["END_DATE"] != original_sale_challenge["endDate"] or \
                sale_challenge["PRODUCTS"] != original_sale_challenge["products"]:
            return True, ""
        return False, "$SALE_CHALLENGE_WITHOUT_MODIFICATIONS"

    return True, ""
