"""
module mqkit.messaging.retry.immediateretrystrategy

This module defines the ImmediateRetryStrategy class, which implements a retry strategy that
immediately retries failed messages up to a specified number of times. If the message continues
to fail after exceeding the maximum retries, it can be forwarded to an optional dead letter
destination for further analysis or handling.
"""

import traceback
from typing import override

from ...errors import ConfigurationError, MarshalError
from ..exceptionhistoryentry import ExceptionHistoryEntry
from ..forward import Forward
from .retrycontext import RetryContext
from .retrystrategy import RetryStrategy


class ImmediateRetryStrategy(RetryStrategy):
    """
    class ImmediateRetryStrategy

    Implements a retry strategy that immediately retries failed messages up to a specified number
    of times. If the message continues to fail after exceeding the maximum retries, it can be
    forwarded to an optional dead letter destination for further analysis or handling.
    """

    # pylint: disable=too-few-public-methods

    _retries: int

    def __init__(
        self: "ImmediateRetryStrategy",
        retries: int,
    ) -> None:
        super().__init__()

        if retries < 0:
            raise ConfigurationError(
                "Retries must be a non-negative integer",
            )

        self._retries = retries

    @override
    def handle_failure(self: "ImmediateRetryStrategy", context: RetryContext) -> None:
        # first, check if the failure was due to a marshalling error. if so, we assume
        # the message was malformed and don't try to retry it
        if issubclass(type(context.exception), MarshalError):
            self._handle_failure_bad_message(context)
            return

        # then, check if we have already exceeded the maximum number of retries. if so,
        # acknowledge failure and do not retry
        if context.message.attributes.retry_count >= self._retries:
            self._handle_failure_retries_exceeded(context)
            return

        # otherwise, submit the message for retry in the origin queue. then, nack the
        # original message to remove it from the queue since we have requeued it
        self._submit_for_retry(context)
        context.connection.acknowledge_failure(context.message)

    def _append_exception_to_history(
        self: "ImmediateRetryStrategy", context: RetryContext
    ) -> None:
        # append an object representing the current exception to the history
        context.message.attributes.exception_history.append(
            ExceptionHistoryEntry(
                exception_type=type(context.exception).__qualname__,
                exception_module=type(context.exception).__module__,
                exception_message=str(context.exception),
                traceback=traceback.format_exception(context.exception),
                retry_count=context.message.attributes.retry_count,
            )
        )

    def _forward_to_dlq(self: "ImmediateRetryStrategy", context: RetryContext) -> None:
        if context.dead_letter_destination is None:
            self._logger.warning(
                "No dead letter destination configured, message will be discarded"
            )
        else:
            self._logger.info(
                "Forwarding failed message to dead letter destination: %s",
                context.dead_letter_destination,
            )

            # set up the message with the appropriate dlq context
            if context.dead_letter_destination.topic is not None:
                context.message.attributes.topic = context.dead_letter_destination.topic
            context.message.attributes.headers["x-mqkit-previous-retry-count"] = str(
                context.message.attributes.retry_count
            )
            context.message.attributes.headers |= {
                "x-mqkit-forwarded": "true",
                "x-mqkit-dead-letter": "true",
                "x-mqkit-origin-queue": context.received_queue,
            }
            self._append_exception_to_history(context)
            context.message.attributes.retry_count = 0

            context.connection.forward_message(
                Forward(
                    forward_target=context.dead_letter_destination.resource,
                    message=context.message,
                )
            )

    def _handle_failure_bad_message(
        self: "ImmediateRetryStrategy", context: RetryContext
    ) -> None:
        self._logger.error(
            "Message handling failed with marshal error: %s. "
            "Will not retry due to malformed message data",
            context.exception,
        )
        self._forward_to_dlq(context)
        context.connection.acknowledge_failure(context.message)

    def _handle_failure_retries_exceeded(
        self: "ImmediateRetryStrategy", context: RetryContext
    ) -> None:
        self._logger.info(
            "Message handling failed with exception: %s. "
            "Exceeded maximum retry count of %s (current retry count: %s). "
            "Acknowledging failure and will not retry",
            context.exception,
            self._retries,
            context.message.attributes.retry_count,
        )

        # forward a copy of the message to the dead letter destination if configured
        # then acknowledge failure on the original message to remove it from the queue
        self._forward_to_dlq(context)
        context.connection.acknowledge_failure(context.message)

    def _submit_for_retry(
        self: "ImmediateRetryStrategy", context: RetryContext
    ) -> None:
        # submit the message for retry by requeuing it with an incremented retry count
        # in the headers
        self._append_exception_to_history(context)
        context.message.attributes.retry_count += 1
        context.connection.submit_message(context.message)
        self._logger.info(
            "Requeued message for immediate retry (current retry count: %s)",
            context.message.attributes.retry_count,
        )
