import json
from logging import Logger
from typing import Any, Optional

from messagebus import Event, Message, MessageBus
from messageprocessorutil.data_serializer import DataSerializer, FunctionDataSerializer, SerializerLike
from messageprocessorutil.parser import FunctionParser, Parser, ParserLike
from messageprocessorutil.processor import UuidMessageProcessor
from messageprocessorutil.sender import Sender

from ..sender import S3Sender
from ..serializer import ReportLayoutSerializer


class ReportProcessor(UuidMessageProcessor):
    """Base processor for BOH report events with pluggable parser, serializer and sender.

    By default, ``call_business`` delegates to ``interactor.execute(request)``. Subclasses
    can override it for custom logic. Parser and Serializer accept ABC instances or plain
    callables, adapted automatically. The default sender is ``S3Sender``.
    """

    def __init__(
        self,
        report_name: str,
        parser: ParserLike,
        logger: Logger,
        interactor: Optional[Any] = None,
        serializer: Optional[SerializerLike] = None,
        sender: Optional[Sender] = None,
    ):
        """Creates a ReportProcessor.

        Args:
            report_name: Name used in log messages and error responses.
            parser: Parser or callable ``(data: dict, event_type: str) -> request``.
            logger: Logger for this processor, also passed to the default sender.
            interactor: Object with ``execute(request)``. Used by the default ``call_business``.
            serializer: DataSerializer or callable ``(result: Any) -> dict``. Defaults to ``ReportLayoutSerializer``.
            sender: Sender for dispatching responses. Defaults to ``S3Sender``.
        """
        super().__init__()
        self.report_name = report_name
        self.interactor = interactor
        self.sender = sender or S3Sender(logger=logger)
        self.parser = parser if isinstance(parser, Parser) else FunctionParser(parser)
        self.serializer = (
            ReportLayoutSerializer(report_name=report_name)
            if serializer is None
            else serializer
            if isinstance(serializer, DataSerializer)
            else FunctionDataSerializer(serializer)
        )
        self.logger = logger
        self.original_payload: dict = {}

    @property
    def report_id(self):
        """str: The report-id extracted from the original payload."""
        return self.original_payload.get("report-id")

    def parse_data(self, data: bytes, event_type: str) -> Any:
        self.original_payload = json.loads(data.decode("utf-8")) if data else {}
        return self.parser.parse(data=self.original_payload, event_type=event_type)

    def call_business(self, request: Any) -> Any:
        self.logger.info("Started %s report data with id: %s", self.report_name, self.report_id)
        return self.interactor.execute(request)

    def format_response(
        self, message_bus: MessageBus, message: Message, event: Event, input_obj: Any, result: Any
    ) -> None:
        self.logger.info("Report %s ID %s data successful fetched, replying event", self.report_name, self.report_id)
        self.sender.send(
            message_bus=message_bus,
            original_event=event,
            data=self.serializer.serialize(result),
            original_payload=self.original_payload,
        )

    def format_exception(
        self, message_bus: MessageBus, message: Message, event: Event, input_obj: Any, exception: Exception
    ) -> None:
        self.logger.exception(
            "Error while processing %s Report ID %s", self.report_name, self.report_id, exc_info=exception
        )
        self.sender.send_error(
            message_bus=message_bus,
            original_event=event,
            data=self.serializer.serialize_error(
                input_obj=input_obj, exception=exception, original_payload=self.original_payload
            ),
            original_payload=self.original_payload,
        )

    def format_parse_exception(
        self, message_bus: MessageBus, message: Message, event: Event, data: str, exception: Exception
    ) -> None:
        self.logger.exception("Error while parsing %s report request", self.report_name, exc_info=exception)
        self.sender.send_parser_error(
            message_bus=message_bus,
            original_event=event,
            data=self.serializer.serialize_parser_error(data=data, exception=exception),
            original_payload=self.original_payload,
        )
