from __future__ import annotations

import traceback
from abc import ABC, abstractmethod
from datetime import datetime
from sys import stdout
from typing import Any


class MinerShieldEvent:
    """
    Class describing event, which happened in the shield.
    """

    date: datetime  # Date of the event.
    template: str  # Template of the event.
    exception: Exception | None  # Exception which caused the event.
    metadata: dict[str, Any]  # Additional metadata.
    description: str  # Description of the event (template filled with metadata).

    def __init__(self, template: str, exception: Exception | None = None, **metadata):
        """
        Args:
            template: Template of the event (used to generate description).
            exception: Exception which caused the event.
            metadata: Additional metadata (used also to generate description).
        """
        self.date = datetime.now()
        self.template = template
        self.exception = exception
        self.metadata = metadata
        self.description = template.format(**metadata)


class AbstractMinerShieldEventProcessor(ABC):
    """
    Abstract base class for processor handling events generated by shield.
    """

    def event(self, template: str, exception: Exception | None = None, **kwargs):
        """
        Add event to event processor.

        Args:
            template: Description template to be filled using kwargs.
            exception: Exception to be attached to event.
            kwargs: Event params. Used also to fill template.
        """
        return self._add_event(MinerShieldEvent(template, exception, **kwargs))

    @abstractmethod
    def _add_event(self, event: MinerShieldEvent):
        pass


class PrintingMinerShieldEventProcessor(AbstractMinerShieldEventProcessor):
    """
    Event processor which logs events to console.
    """

    def _add_event(self, event: MinerShieldEvent):
        print(f'{event.date}: MinerShieldEvent: {event.description}\nMetadata: {event.metadata}')
        if event.exception is not None:
            print('Exception happened:')
            traceback.print_exception(event.exception, file=stdout)
