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_REPORT_JSON_PARSER,
    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: Optional[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 request_report_json_parser(
        self,           # type: NReports
        pos_id,         # type: Optional[int]
        report_json,    # type: str
    ):
        # type: (...) -> str

        request = {
            "posId": int(pos_id) if pos_id is not None else None,
            "reportJson": report_json,
        }
        request = json.dumps(request).encode("utf-8")

        reply = self.message_bus.send_message(
            component="nReports",
            message=Message(
                token=TK_NREPORTS_REPORT_JSON_PARSER,
                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]
        fiscal_coupon=True,    # 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,
            fiscal_coupon=fiscal_coupon,
        )

    def fiscal_coupon(
        self,       # type: NReports
        pos_id,     # type: int
        order_id,   # type: int
    ):
        # type: (...) -> str

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

    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 kitchen_label(
        self,                # type: NReports
        production_order,    # type: str
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=None,
            report_name=Reports.kitchen_label,
            production_order=production_order,
        )

    def delivery_fee(
        self,          # type: NReports
        pos_id,        # type: int
        since_date,    # type: str
        until_date,    # type: str
    ):
        # type: (...) -> str

        return self.request_report(
            pos_id=pos_id,
            report_name=Reports.delivery_fee,
            since_date=since_date,
            until_date=until_date,
        )

    def delivery_coupon(
        self,       # type: NReports
        pos_id,     # type: int
        order_id,   # type: int
    ):
        # type: (...) -> str

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

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

        request = {
            "posId": int(pos_id) if pos_id is not None else None,
            "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:])
