"""Core logging handlers and formatters for Microsoft Teams integration."""

import json
import queue
from collections import defaultdict
from logging import NOTSET, Formatter, Handler, LogRecord
from logging.handlers import QueueHandler, QueueListener
from traceback import format_exception
from typing import Iterable

import requests

__all__ = [
    "TeamsHandler",
    "TeamsQueueHandler",
    "Office365CardFormatter",
    "TeamsCardsFormatter",
    "TeamsAdaptiveCardFormatter",
]


class TeamsCardsFormatter(Formatter):
    """
    This class is the base class for cards formatters.
    https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/what-are-cards
    """

    def format(self, record: LogRecord) -> str:
        raise NotImplementedError()


class TeamsHandler(Handler):
    """
    Logging handler for Microsoft Teams webhook integration.

    This handler blocks operation. Use non-blocking NBTeamsHandler for less
    impact on your application performance.
    """

    def __init__(self, url, level=NOTSET):
        """
        :param url: Microsoft Teams incoming webhook url.
        :param level: Logging level (INFO, DEBUG, ERROR...etc)
        """
        super().__init__(level=level)
        self.url = url

    def format(self, record: LogRecord) -> str:
        if not isinstance(self.formatter, TeamsCardsFormatter):
            return json.dumps({"text": super().format(record)})
        else:
            return self.formatter.format(record)

    def emit(self, record: LogRecord):
        try:
            data = self.format(record)
            requests.post(
                url=self.url,
                headers={"Content-Type": "application/json"},
                data=data,
                timeout=3,
            )
        except requests.RequestException:
            self.handleError(record)


class TeamsQueueHandler(QueueHandler):
    """
    Non-blocking logging handler for Microsoft Teams webhook integration.

    This handler reduces impact on your application compared to the (blocking)
    TeamsHandler.

    Queue-based implementation from
    https://docs.python.org/3/howto/logging-cookbook.html#dealing-with-handlers-that-block
    """

    def __init__(self, url, level=NOTSET):
        self._log_queue = queue.Queue(-1)
        super().__init__(self._log_queue)

        self._teams_handler = TeamsHandler(url, level)
        teams_log_listener = QueueListener(
            self._log_queue, self._teams_handler
        )
        teams_log_listener.start()

    def setFormatter(self, fmt):
        self._teams_handler.setFormatter(fmt)
        super().setFormatter(fmt)


class Office365CardFormatter(TeamsCardsFormatter):
    """
    This formatter formats log records as a simple Office 365 connector card.
    The connector card documentation is at
    https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-reference#office-365-connector-card
    In addition to the message, each log record attribute (levelname,
    lineno...etc) can be displayed as facts.
    """

    _facts = {"name", "levelname", "levelno", "lineno"}
    _color_map = defaultdict(
        lambda: "#008000",
        {
            "DEBUG": "#0000FF",  # blue
            "INFO": "#008000",  # green
            "WARNING": "#FFA500",  # orange
            "ERROR": "#FF0000",  # red
            "CRITICAL": "#8B0000",  # darkred
        },
    )

    def __init__(self, facts: Iterable[str]):
        """
        :param facts: LogRecord attributes to display as facts in the card.
        """
        self.facts = self._facts.intersection(set(facts))
        super().__init__()

    def format(self, record: LogRecord) -> str:
        message = record.getMessage()
        if record.exc_info:
            etype, value, tb = record.exc_info
            message += "\n" * 2
            message += "<code>"
            message += "".join(format_exception(etype, value, tb))
            message += "</code>"
        return json.dumps(
            {
                "@context": "https://schema.org/extensions",
                "@type": "MessageCard",
                "title": f"{record.levelname.title()} in {record.module}",
                "summary": f"{record.getMessage()}",
                "sections": [{"facts": self._build_facts_list(record)}],
                # Fallback to INFO color if needed
                "themeColor": self._color_map[record.levelname],
                "text": message,
            }
        )

    def _build_facts_list(self, record: LogRecord):
        return [
            {"name": fact, "value": getattr(record, fact)}
            for fact in self.facts
        ]


class TeamsAdaptiveCardFormatter(TeamsCardsFormatter):
    """
    This formatter formats log records as a simple adaptive card.
    The card documentation is at
    https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/design-effective-cards
    In addition to the message, each log record attribute (levelname,
    lineno...etc) can be displayed as facts.
    """

    _facts = {"name", "levelname", "levelno", "lineno"}
    _color_map = defaultdict(
        lambda: "#008000",
        {
            "DEBUG": "Accent",  # blue
            "INFO": "Good",  # green
            "WARNING": "Warning",  # orange
            "ERROR": "Attention",  # red
            "CRITICAL": "Dark",  # darkred
        },
    )

    def __init__(self, facts: Iterable[str]):
        """
        :param facts: LogRecord attributes to display as facts in the card.
        """
        self.facts = self._facts.intersection(set(facts))
        super().__init__()

    def format(self, record: LogRecord) -> str:
        message = record.getMessage()
        if record.exc_info:
            etype, value, tb = record.exc_info
            message += "\n" * 2
            message += "<code>"
            message += "".join(format_exception(etype, value, tb))
            message += "</code>"
        return json.dumps(
            {
                "type": "message",
                "attachments": [
                    {
                        "contentType": (
                            "application/vnd.microsoft.card.adaptive"
                        ),
                        "contentUrl": None,
                        "content": {
                            "$schema": (
                                "http://adaptivecards.io/schemas/"
                                "adaptive-card.json"
                            ),
                            "version": "1.2",
                            "type": "AdaptiveCard",
                            "body": [
                                {
                                    "type": "TextBlock",
                                    "size": "Medium",
                                    "weight": "Bolder",
                                    "text": (
                                        f"{record.levelname.title()} in "
                                        f"{record.module}"
                                    ),
                                    "color": self._color_map[record.levelname],
                                    "horizontalAlignment": "Center",
                                },
                                {
                                    "type": "TextBlock",
                                    "text": f"{record.getMessage()}",
                                },
                                {
                                    "type": "FactSet",
                                    "facts": self._build_facts_list(record),
                                },
                                {
                                    "type": "RichTextBlock",
                                    "inlines": [
                                        {"type": "TextRun", "text": message}
                                    ],
                                },
                            ],
                        },
                    }
                ],
            }
        )

    def _build_facts_list(self, record: LogRecord):
        return [
            {"title": fact, "value": getattr(record, fact)}
            for fact in self.facts
        ]
