import json
from typing import Optional

from messagebus import (
    DataType,
    DefaultToken,
    Message,
    MessageBus,
)
from .shared import error
from .shared.exception import NReportsException
from .shared.reports import Reports
from .shared.token import TK_NREPORTS_BUILD_REPORT


class NReports(object):

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

        self.message_bus = message_bus

    def request_report(
        self,           # type: NReports
        pos_id,         # type: int
        report_name,    # type: str
        **kwargs
    ):
        # type: (...) -> str

        request = self._build_request_data(
            pos_id=pos_id,
            report_name=report_name,
            **kwargs
        )

        reply = self.message_bus.send_message(
            component="nReports",
            message=Message(
                token=TK_NREPORTS_BUILD_REPORT,
                data_type=DataType.json,
                data=request,
            )
        )

        if reply.token == DefaultToken.TK_SYS_ACK:
            return reply.data

        raise self._build_exception_from_reply(reply=reply)

    def canceled_fiscal_coupon(
        self,           # type: NReports
        pos_id,         # type: int
        order_id,       # type: int
        is_mobile=False,   # type: Optional[bool]
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.canceled_fiscal_coupon,
            order_id=order_id,
            is_mobile=is_mobile,
        )

    def canceled_receipt(
        self,               # type: NReports
        pos_id,             # type: int
        order_id,           # type: int
        is_mobile=False,    # type: Optional[bool]
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.canceled_receipt,
            order_id=order_id,
            is_mobile=is_mobile,
        )

    def end_of_sale(
        self,           # type: NReports
        pos_id,         # type: int
        order_id,       # type: int
        mobile=False,   # type: Optional[bool]
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.end_of_sale,
            order_id=order_id,
            is_mobile=mobile,
        )

    def pre_account(
        self,                                   # type: NReports
        pos_id,                                 # type: int
        order_id=None,                          # type: Optional[int]
        table_id=None,                          # type: Optional[int]
        is_mobile=False,                        # type: Optional[bool]
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.pre_account,
            order_id=order_id,
            table_id=table_id,
            is_mobile=is_mobile,
        )

    def pre_account_quick_service(
        self,               # type: NReports
        pos_id,             # type: int
        order_id,           # type: int
        is_mobile=False,    # type: Optional[bool]
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.pre_account_quick_service,
            order_id=order_id,
            is_mobile=is_mobile,
        )

    def pre_account_by_seats(
        self,                 # type: NReports
        pos_id,               # type: int
        table_id,             # type: str
        filter_table_seat,    # type: str
        print_by,             # type: str
        is_mobile,            # type: bool
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.pre_account_by_seats,
            table_id=table_id,
            print_by=print_by,
            filter_table_seat=filter_table_seat,
            is_mobile=is_mobile,
        )

    def conference_items_by_seats(
        self,                 # type: NReports
        pos_id,               # type: int
        table_id,             # type: str
        filter_table_seat,    # type: str
        print_by,             # type: str
        is_mobile,            # type: bool
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.conference_items_by_seats,
            table_id=table_id,
            print_by=print_by,
            filter_table_seat=filter_table_seat,
            is_mobile=is_mobile,
        )

    def conference_items(
        self,                                   # type: NReports
        pos_id,                                 # type: int
        order_id=None,                          # type: Optional[int]
        table_id=None,                          # type: Optional[int]
        is_mobile=False,                        # type: Optional[bool]
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.conference_items,
            order_id=order_id,
            table_id=table_id,
            is_mobile=is_mobile,
        )

    def _build_request_data(
        self,           # type: NReports
        pos_id,         # type: int
        report_name,    # type: str
        **kwargs
    ):
        # type: (...) -> bytes

        request = {
            "posId": int(pos_id),
            "reportName": report_name,
        }
        for key, value in kwargs.items():
            request[self._to_camel_case(key)] = value

        return json.dumps(request).encode("utf-8")

    @staticmethod
    def _build_exception_from_reply(
        reply,  # type: Message
    ):
        # type: (...) -> NReportsException

        try:
            data_text = reply.data.decode("utf-8")
        except UnicodeDecodeError:
            data_text = reply.data.hex()

        try:
            response_dict = json.loads(data_text)
            return NReportsException(
                message=response_dict["errorMessage"],
                error_code=response_dict["errorCode"],
            )
        except (Exception,):
            return NReportsException(
                message=data_text,
                error_code=error.UNEXPECTED_ERROR,
            )

    @staticmethod
    def _to_camel_case(
        snake_str,  # type: str
    ):
        # type: (...) -> str

        components = snake_str.split("_")
        return components[0].lower() + "".join(x.lower().title() for x in components[1:])
