import json

from typing import (
    Dict,
    List,
    Sequence,
)

from .error import UpdateItemsError
from .shared.dto import RuptureEventDto
from .shared.dto.exception import InvalidPartCode
from .token import (
    TK_SLCTRL_PMGR_GET_TENDERS,
    TK_SLCTRL_PMGR_GET_PRODUCTS_NAME,
    TK_SLCTRL_PMGR_RUPTURE_UPDATE_DAILY_DISHES,
    TK_SLCTRL_PMGR_RUPTURE_UPDATE_ITEMS,
)

from messagebus import (
    ApplicationErrorMessage,
    MessageBus,
    Message,
    DataType,
    DefaultToken,
)


class ProductService(object):

    def __init__(
        self,               # type: ProductService
        message_bus,        # type: MessageBus
    ):
        # type: (...) -> None

        self.message_bus = message_bus
        self.component_name = "ORDERMGR"

    def get_tenders_list(
        self,       # type: ProductService
    ):
        # type: (...) -> Dict

        msg = self._get_tenders()
        json_data = json.loads(msg.data)
        return _byteify(json_data)

    def get_tenders_dict(
        self,       # type: ProductService
    ):
        # type: (...) -> Dict

        msg = self._get_tenders()
        tenders = json.loads(msg.data)
        tenders_dict = {}
        for tender in tenders:
            tenders_dict[tender["id"]] = tender

        return _byteify(tenders_dict)

    def get_products_name(
        self,   # type: ProductService
    ):
        # type: (...) -> Dict[str, str]

        reply = self.message_bus.send_message(
            component=self.component_name,
            message=Message(token=TK_SLCTRL_PMGR_GET_PRODUCTS_NAME)
        )

        if reply.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError("Error getting products name")

        return json.loads(reply.data.decode())

    def update_items(
        self,           # type: ProductService
        user_id,        # type: int
        enabled,        # type: Sequence[int]
        disabled,       # type: Sequence[int]
    ):
        # type: (...) -> RuptureEventDto

        reply = self.message_bus.send_message(
            component=self.component_name,
            message=Message(
                token=TK_SLCTRL_PMGR_RUPTURE_UPDATE_ITEMS,
                data=json.dumps({
                    "user_id": user_id,
                    "enabled": enabled,
                    "disabled": disabled,
                }).encode()
            )
        )

        if reply.token == DefaultToken.TK_SYS_APPLICATION_ERROR:
            application_error = ApplicationErrorMessage.build_from_message(message=reply)
            if application_error.error_code == UpdateItemsError.INVALID_PART_CODE.code:
                raise InvalidPartCode(part_code=int(application_error.error_message))

        data_dict = json.loads(reply.data.decode())
        return RuptureEventDto.build_from_dict(data_dict)

    def update_daily_dishes(
        self,       # type: ProductService
        user_id,    # type: int
        to_add,     # type: List[Dict[str, int]]
        to_remove,  # type: List[int]
    ):
        # type: (...) -> None

        dishes_changes_json = json.dumps({
            "userId": user_id,
            "unset": to_remove,
            "set": to_add,
        }).encode()

        reply = self.message_bus.send_message(
            component=self.component_name,
            message=Message(
                token=TK_SLCTRL_PMGR_RUPTURE_UPDATE_DAILY_DISHES,
                data=dishes_changes_json,
            ),
        )

        if reply.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError("Could not update daily dishes")

        return None

    def _get_tenders(
        self,       # type: ProductService
    ):
        # type: (...) -> Message

        msg = self.message_bus.send_message(
            component=self.component_name,
            message=Message(
                token=TK_SLCTRL_PMGR_GET_TENDERS,
                data_type=DataType.param,
            )
        )

        if msg.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError("Could not retrieve tenders")

        return msg


def _byteify(input):
    if isinstance(input, dict):
        return {_byteify(key): _byteify(value) for key, value in input.iteritems()}

    if isinstance(input, list):
        return [_byteify(element) for element in input]

    if isinstance(input, str):
        return input.encode()

    return input
