import json
import logging
from abc import ABC
from typing import (
    Callable,
    Optional,
)

from messagebus import (
    AckMessage,
    ApplicationErrorMessage,
    BadRequestMessage,
    Event,
    InternalErrorMessage,
    Message,
    MessageBus,
)
from messageprocessorutil import UuidMessageProcessor
from model.exceptions import (
    ConditionMustBeAVariable,
    CurrencyValueNotANumber,
    ErrorBuildingDataBuilder,
    ErrorBuildingReportData,
    ErrorFormattingReport,
    ErrorLoadingPosTranslations,
    ErrorParsingReportData,
    InnerListAttributeIsNotAList,
    InnerListMustHaveNameAttr,
    InvalidCurrencyParameterCount,
    InvalidDateTimeDateParameter,
    InvalidDatetimeNowParameterCount,
    InvalidDatetimeParameterCount,
    InvalidNumberParameterCount,
    InvalidTextFormatParameterCount,
    InvalidTextFormatSideParameterValue,
    NumberValueNotANumber,
    ObjectListValueIsNotAList,
    PaddingNotANumber,
    ReportFileNotFound,
    ReportFunctionNotImplemented,
    ReportNotImplemented,
    TextFormatLengthMustBeInteger,
    TextListAttributeIsNotAList,
    VariableNotGeneratedByBuilder,
)
from nreports_api.shared import error
from nreports_api.shared.report_dto import ReportDto

logger = logging.getLogger(__name__)


class BaseNReportProcessor(UuidMessageProcessor, ABC):

    def __init__(
        self,
    ) -> None:
        super().__init__()

    def format_response(
        self,
        message_bus: MessageBus,
        message: Message,
        event: Event,
        request: Callable,
        result: ReportDto,
    ) -> None:

        data = json.dumps(
            obj=result,
            indent=4,
            sort_keys=True,
        ).encode()

        logger.info(f"Report data: {result}")
        reply = AckMessage(data=data)
        message_bus.reply_message(
            message=message,
            reply_message=reply,
        )

    def format_exception(
        self,
        message_bus: MessageBus,
        message: Message,
        event: Event,
        request: Optional[Callable],
        exception: Exception,
    ) -> None:

        error_message = str(exception)
        error_code = self._get_error_code(exception=exception)
        if error_code is None:
            reply = InternalErrorMessage(ex=exception)
        else:
            reply = ApplicationErrorMessage(
                error_code=error_code,
                error_message=error_message,
            )

        message_bus.reply_message(
            message=message,
            reply_message=reply,
        )

    def format_parse_exception(
        self,
        message_bus: MessageBus,
        message: Message,
        event: Event,
        data: bytes,
        exception: Exception,
    ) -> None:

        error_message = f"Failed to parse data: {str(data)}. Error: {str(exception)}"
        error_code = self._get_error_code(exception=exception)
        if error_code is None:
            resp = InternalErrorMessage(ex=exception)
        else:
            resp = BadRequestMessage(
                error_code=error_code,
                error_message=error_message,
            )

        message_bus.reply_message(
            message=message,
            reply_message=resp,
        )

    def _get_error_code(
        self,
        exception: Exception,
    ) -> Optional[int]:

        exception_map = {
            self.InvalidJson: error.INVALID_JSON,
            self.MissingReportName: error.MISSING_REQUEST_NAME,
            ConditionMustBeAVariable: error.CONDITION_IS_NOT_A_VARIABLE,
            CurrencyValueNotANumber: error.CURRENCY_VALUE_NOT_A_NUMBER,
            ErrorBuildingDataBuilder: error.ERROR_BUILDING_DATA_BUILDER,
            ErrorBuildingReportData: error.ERROR_BUILDING_REPORT_DATA,
            ErrorFormattingReport: error.ERROR_FORMATTING_REPORT,
            ErrorLoadingPosTranslations: error.COULD_NOT_LOAD_POS_TRANSLATIONS,
            ErrorParsingReportData: error.ERROR_PARSING_REPORT_DATA,
            InnerListAttributeIsNotAList: error.INNER_LIST_ATTRIBUTE_IS_NOT_A_LIST,
            InnerListMustHaveNameAttr: error.INNER_LIST_MUST_HAVE_NAME,
            InvalidCurrencyParameterCount: error.INVALID_CURRENCY_PARAMETERS_COUNT,
            InvalidDateTimeDateParameter: error.DATETIME_DATE_NOT_A_ISO_DATE,
            InvalidDatetimeNowParameterCount: error.DATETIME_NOW_CANNOT_RECEIVE_PARAMETER,
            InvalidDatetimeParameterCount: error.INVALID_DATETIME_PARAMETERS_COUNT,
            InvalidNumberParameterCount: error.INVALID_NUMBER_PARAMETERS_COUNT,
            InvalidTextFormatParameterCount: error.INVALID_TEXT_FORMAT_PARAMETERS_COUNT,
            InvalidTextFormatSideParameterValue: error.INVALID_TEXT_FORMAT_SIDE_PARAMETER,
            NumberValueNotANumber: error.NUMBER_VALUE_NOT_A_NUMBER,
            ObjectListValueIsNotAList: error.VALUE_RETURNED_FROM_OBJECT_LIST_IS_NOT_A_LIST,
            PaddingNotANumber: error.PADDING_IS_NOT_A_NUMBER,
            ReportFileNotFound: error.REPORT_FILE_NOT_FOUND,
            ReportFunctionNotImplemented: error.REPORT_FUNCTION_NOT_IMPLEMENTED,
            ReportNotImplemented: error.REPORT_NOT_IMPLEMENTED,
            TextFormatLengthMustBeInteger: error.TEXT_FORMAT_LENGTH_NOT_INTEGER,
            TextListAttributeIsNotAList: error.TEXT_LIST_ATTRIBUTE_IS_NOT_A_LIST,
            VariableNotGeneratedByBuilder: error.VARIABLE_WAS_NOT_GENERATED_BY_THE_BUILDER,
        }

        return exception_map.get(type(exception))  # noqa

    class MissingReportName(Exception):

        def __init__(self):
            super().__init__("Missing 'reportName' attribute'")

    class InvalidJson(Exception):

        def __init__(self):
            super().__init__("Invalid JSON file")
