from typing import Mapping  # noqa:F401
from typing import Optional  # noqa:F401
from typing import Union  # noqa:F401

from ddtrace.internal.logger import get_logger
from ddtrace.internal.utils.cache import cachedmethod
from ddtrace.internal.utils.http import normalize_header_name


log = get_logger(__name__)


class HttpConfig(object):
    """
    Configuration object that expose an API to set and retrieve both global and integration specific settings
    related to the http context.
    """

    def __init__(self, header_tags: Optional[Mapping[str, str]] = None) -> None:
        self._header_tags = {normalize_header_name(k): v for k, v in header_tags.items()} if header_tags else {}
        self.trace_query_string = None

    def _reset(self):
        self._header_tags = {}
        self._header_tag_name.cache_clear()

    @cachedmethod()
    def _header_tag_name(self, header_name: str) -> Optional[str]:
        if not self._header_tags:
            return None

        normalized_header_name = normalize_header_name(header_name)
        log.debug("Checking header '%s' tracing in whitelist %s", normalized_header_name, self._header_tags.keys())
        return self._header_tags.get(normalized_header_name)

    @property
    def is_header_tracing_configured(self) -> bool:
        return len(self._header_tags) > 0

    def trace_headers(self, whitelist: Union[list[str], str]) -> Optional["HttpConfig"]:
        """
        Registers a set of headers to be traced at global level or integration level.
        :param whitelist: the case-insensitive list of traced headers
        :type whitelist: list of str or str
        :return: self
        :rtype: HttpConfig
        """
        if not whitelist:
            return None

        whitelist = [whitelist] if isinstance(whitelist, str) else whitelist
        for whitelist_entry in whitelist:
            normalized_header_name = normalize_header_name(whitelist_entry)
            if not normalized_header_name:
                continue
            # Empty tag is replaced by the default tag for this header:
            #  Host on the request defaults to http.request.headers.host
            self._header_tags.setdefault(normalized_header_name, "")

        # Mypy can't catch cached method's invalidate()
        self._header_tag_name.cache_clear()  # type: ignore[attr-defined]

        return self

    def header_is_traced(self, header_name: str) -> bool:
        """
        Returns whether or not the current header should be traced.
        :param header_name: the header name
        :type header_name: str
        :rtype: bool
        """
        return self._header_tag_name(header_name) is not None

    def __repr__(self):
        return (
            f"<{self.__class__.__name__} "
            f"traced_headers={self._header_tags.keys()} "
            f"trace_query_string={self.trace_query_string}>"
        )
