from abc import ABC
from collections.abc import Collection
from typing import Set, Mapping

from model.http.info.IterableContentModel import IterableContent

import json

_SENTINEL = object()


class AbstractHeader(ABC):
    """
    This class represent a basic header implementation. The header class has a heder name
    and a header value like:
    header-name: <my_header_name>
    """
    _my_header_name: str
    _my_header_value: str

    def __init__(self, my_header_name: str = '', my_header_value: str = ''):
        if len(my_header_name) == 0 or len(my_header_value) == 0:
            raise TypeError(f"The header name cannot be None -> {my_header_name},"
                            f" the header value cannot be None -> {my_header_value}.")
        self._my_header_name = my_header_name
        self._my_header_value = my_header_value

    def get_header_name(self) -> str:
        """
        This http retuns the header name.
        :return: see above
        """
        return self._my_header_name

    def get_header_value(self) -> str:
        """
        This http retuns the header value.
        :return: see above
        """
        return self._my_header_value

    def __repr__(self) -> str:
        return f"""
        header-name: <{self._my_header_name}>
        header-value: <{self._my_header_value}>
        """

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, AbstractHeader):
            return False

        other_object: AbstractHeader = other
        if other_object.get_header_name() != self._my_header_name:
            return False

        if other_object.get_header_value() != self._my_header_value:
            return False

        return True

    def __hash__(self) -> int:
        return hash((self._my_header_name, self._my_header_value))


class Header(AbstractHeader):
    """
    Basic header implementation.
    """

    def __init__(self, _sentinel: object = None, *, my_header_name: str, my_header_value: str):
        if _sentinel is not _SENTINEL:
            raise TypeError(
                "In order to create the Header you have to use the factory methods.")
        super().__init__(my_header_name, my_header_value)

    @classmethod
    def from_tuple(cls, value: tuple[str, str] = ('', '')) -> 'AbstractHeader':
        """
        Factory http for creating a Header object from a tuple of two strings.
        :param value: input tuple of two strings in the format HEADER-NAME: HEADER-VALUE
        :return: a new Header object
        """
        if not value or len(value) != 2:
            raise ValueError('The input value must not be empty and not None.')
        return cls(_sentinel=_SENTINEL, my_header_name=value[0], my_header_value=value[1])

    @classmethod
    def from_key_value(cls, key: str = '', value: str = '') -> 'AbstractHeader':
        if key == '' or value == '':
            raise ValueError('The key and value must not be empty.')
        return cls(_sentinel=_SENTINEL, my_header_name=key, my_header_value=value)


class Headers(IterableContent[Mapping[str, str]]):
    """
    This class represents a collection of headers.
    """

    def __init__(self, _sentinel: object = None, *, headers: Set[AbstractHeader]):
        if _sentinel is not _SENTINEL:
            raise TypeError(
                "In order to create the Headers class you have to use the factory methods.")
        self._my_headers = set()
        for header in headers:
            self._my_headers.add(header)

    def get_headers_set(self) -> Set[AbstractHeader]:
        return self._my_headers

    def dump(self) -> Mapping[str, str]:
        return {header.get_header_name(): header.get_header_value() for header in self._my_headers}

    def __repr__(self) -> str:
        output: str = ""
        for header in self._my_headers:
            output += f"\t<{header.get_header_name()}>: <{header.get_header_value()}>\n"
        return output

    @classmethod
    def from_list(cls, headers: Collection[AbstractHeader]) -> 'Headers':
        """
        Factory http for creating a Headers object from a list of header. This factory http avoid to collect None headers.
        :param headers: input collection of headers
        :return: a new Headers object
        """
        if len(headers) == 0:
            raise ValueError('The input collection must not be empty.')
        return cls(_sentinel=_SENTINEL, headers={header for header in headers if header})

    @classmethod
    def empty(cls) -> 'Headers':
        return cls(_sentinel=_SENTINEL, headers=set())

    @classmethod
    def from_dictionary(cls, headers: Mapping[str, str]) -> 'Headers':
        return cls(_sentinel=_SENTINEL, headers={Header.from_key_value(key, value) for key, value in headers.items()})

    @classmethod
    def from_string(cls, content: str) -> 'Headers':
        try:
            headers: Mapping[str, str] = json.loads(content)
            return cls(_sentinel=_SENTINEL, headers={Header.from_key_value(key, value) for key, value in headers.items()})
        except json.JSONDecodeError:
            raise ValueError('The input content is not a valid json string.')
