import json
from datetime import datetime

import iso8601

from typing import (
    Optional,
    Union,
)

from bustoken import (
    TK_GET_DEVICES,
    TK_UPDATE_DEVICE_STATUS,
)
from mw_helper import show_device_manager_dialog
from msgbus import (
    MBException,
    TK_SYS_ACK,
    FM_PARAM,
)
from sysactions import (
    action,
    get_model,
    translate_message,
    show_messagebox,
    get_custom,
)

from .. import (
    mb_context,
    logger,
)
from model.customexception import (
    ComponentNotFound,
    ErrorGettingDevicesData,
)
from ._DeviceStatus import DeviceStatus
from ..util import get_i18n
from ..util.convert_to_localtime import convert_to_localtime


@action
def show_device_manager(
    pos_id,  # type: str
):
    # type: (...) -> None

    resp = True
    while resp:
        devices = _get_devices_statuses(pos_id=pos_id)
        body = _parse_devices_data(
            pos_id=pos_id,
            devices=devices,
        )
        resp = show_device_manager_dialog(
            pos_id=pos_id,
            info_text=body,
        )
        if not resp:
            return

        _update_device_statuses(
            pos_id=pos_id,
            devices=devices,
            devices_data=resp,
        )


def _parse_devices_data(
    pos_id,     # type: Union[unicode, str]
    devices,    # type: str
):
    # type: (...) -> object

    model = get_model(posid=pos_id)
    options_header = "$TYPE_LABEL#$NAME_LABEL#$STATUS_LABEL#$LAST_UPDATE#$ACTIONS"
    body = {'header': options_header, 'rows': []}
    devices = json.loads(devices)['devices']
    for device_data in devices:
        device = DeviceStatus.from_json(device_data=device_data)
        status_label = "$CONNECTED" if device.status is True else "$DISCONNECTED"
        status_label = translate_message(model, status_label)
        operator = device.operator.decode('UTF-8') if device.operator else device.operator
        time = device.time
        if time:
            time = _get_formatted_datetime_in_localtime(
                pos_id=pos_id,
                date=time,
            )

        last_updated = "{} - {}".format(operator, time) if operator else '-'
        line = "{}#{}#{}#{}#{}".format(device.type, device.name, status_label, last_updated, device.status)
        body['rows'].append(line)

    return json.dumps(body)


def _update_device_statuses(
    pos_id,         # type: str
    devices,        # type: str
    devices_data,   # type: Union[unicode, str]
):
    # type: (...) -> None

    devices_data = json.loads(devices_data)
    devices = json.loads(devices)
    model = get_model(posid=pos_id)
    operator = get_custom(
        model=model,
        key="Last Manager ID",
    )
    request = {
        "devices": [],
        "operator": operator,
    }
    for device_data in devices['devices']:
        device = DeviceStatus.from_json(device_data=device_data)
        new_status = [d for d in devices_data if device.id in d.keys()][0][device.id]
        if device.status != new_status:
            request["devices"].append(
                {
                    "deviceName": device.name,
                    "deviceType": device.type,
                    "enabled": new_status,
                }
            )

    _update_devices(
        pos_id=pos_id,
        request=json.dumps(request).encode("utf-8"),
    )


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

    try:
        ret = mb_context.MB_EasySendMessage(
            dest_name='DeviceManager',
            token=TK_GET_DEVICES,
        )
        if ret.token != TK_SYS_ACK:
            show_messagebox(
                posid=pos_id,
                message="$GET_DEVICES_DATA_FAILED",
                title="$ERROR",
            )
            logger.exception("Error getting devices: Token: {} / Data: {}".format(ret.token, ret.data))
            raise ErrorGettingDevicesData()
        return ret.data
    except MBException as ex:
        if "SE_NOTFOUND" in ex.message:
            raise ComponentNotFound("DeviceManager")


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

    try:
        ret = mb_context.MB_EasySendMessage(
            dest_name="DeviceManager",
            token=TK_UPDATE_DEVICE_STATUS,
            format=FM_PARAM,
            data=request,
        )
        if ret.token == TK_SYS_ACK:
            show_messagebox(
                posid=pos_id,
                message="$UPDATE_DEVICES_SUCCESS",
            )
        else:
            show_messagebox(
                posid=pos_id,
                message="$ERROR_UPDATING_DEVICES",
                title="$ERROR",
            )
            logger.exception("Error updating devices: Token: {} / Data: {}".format(ret.token, ret.data))
            return
    except MBException as ex:
        if "SE_NOTFOUND" in ex.message:
            raise ComponentNotFound("DeviceManager")


def _get_formatted_datetime_in_localtime(
    pos_id,  # type: str
    date,    # type: str
):
    # type: (...) -> str

    enabled_time = iso8601.parse_date(date)
    enabled_time_local_time = convert_to_localtime(enabled_time)
    format_to_internationalize = "#DATETIME({})".format(enabled_time_local_time.isoformat())
    return get_i18n(pos_id=int(pos_id)).internationalize_text(format_to_internationalize)
