""" Constant objects used by FediBlockHole
"""
import enum
from typing import NamedTuple, Optional, TypedDict
from dataclasses import dataclass

import logging
log = logging.getLogger('fediblockhole')

class SeverityLevel(enum.IntEnum):
    """How severe should a block be? Higher is more severe.
    """
    NONE = enum.auto()
    SILENCE = enum.auto()
    SUSPEND = enum.auto()

class BlockSeverity(object):
    """A representation of a block severity

    We add some helpful functions rather than using a bare IntEnum
    """

    def __init__(self, severity:str=None):
        self._level = self.str2level(severity)

    @property
    def level(self):
        return self._level

    @level.setter
    def level(self, value):
        if isinstance(value, SeverityLevel):
            self._level = value
        elif type(value) == type(''):
            self._level = self.str2level(value)
        else:
            raise ValueError(f"Invalid level value '{value}'")

    def str2level(self, severity:str=None):
        """Convert a string severity level to an internal enum"""

        if severity in [None, '', 'noop']:
            return SeverityLevel.NONE

        elif severity in ['silence']:
            return SeverityLevel.SILENCE
        
        elif severity in ['suspend']:
            return SeverityLevel.SUSPEND

        else:
            raise ValueError(f"Invalid severity value '{severity}'")

    def __repr__(self):
        return f"'{str(self)}'"

    def __str__(self):
        """A string version of the severity level
        """
        levelmap = {
            SeverityLevel.NONE: 'noop',
            SeverityLevel.SILENCE: 'silence',
            SeverityLevel.SUSPEND: 'suspend',
        }
        return levelmap[self.level]

    def __lt__(self, other):
        if self._level < other._level:
            return True

    def __gt__(self, other):
        if self._level > other._level:
            return True

    def __eq__(self, other):
        if other is not None and self._level == other._level:
            return True

    def __le__(self, other):
        if self._level <= other._level:
            return True

    def __ge__(self, other):
        if self._level >= other._level:
            return True

# class _DomainBlock(NamedTuple):
#     domain: str # FIXME: Use an actual Domain object from somewhere?
#     severity: BlockSeverity = BlockSeverity.SUSPEND
#     public_comment: str = ''
#     private_comment: str = ''
#     reject_media: bool = False
#     reject_reports: bool = False
#     obfuscate: bool = False

class DomainBlock(object):

    fields = [
        'domain',
        'severity',
        'public_comment',
        'private_comment',
        'reject_media',
        'reject_reports',
        'obfuscate',
    ]

    all_fields = [
        'domain',
        'severity',
        'public_comment',
        'private_comment',
        'reject_media',
        'reject_reports',
        'obfuscate',
        'id'
    ]

    def __init__(self, domain:str,
            severity: BlockSeverity=BlockSeverity('suspend'),
            public_comment: str="",
            private_comment: str="",
            reject_media: bool=False,
            reject_reports: bool=False,
            obfuscate: bool=False,
            id: int=None):
        """Initialize the DomainBlock
        """        
        self.domain = domain
        self.severity = severity
        self.public_comment = public_comment
        self.private_comment = private_comment
        self.reject_media = reject_media
        self.reject_reports = reject_reports
        self.obfuscate = obfuscate
        self.id = id

    @property
    def severity(self):
        return self._severity

    @severity.setter
    def severity(self, sev):
        if isinstance(sev, BlockSeverity):
            self._severity = sev
        else:
            self._severity = BlockSeverity(sev)

    def _asdict(self):
        """Return a dict version of this object
        """
        dictval = {
            'domain': self.domain,
            'severity': str(self.severity),
            'public_comment': self.public_comment,
            'private_comment': self.private_comment,
            'reject_media': self.reject_media,
            'reject_reports': self.reject_reports,
            'obfuscate': self.obfuscate,
        }
        if self.id:
            dictval['id'] = self.id

        return dictval

    def compare_fields(self, other, fields=None)->list:
        """Compare two DomainBlocks on specific fields.
        If all the fields are equal, the DomainBlocks are equal.

        @returns: a list of the fields that are different
        """
        if not isinstance(other, DomainBlock):
            raise ValueError(f"Cannot compare DomainBlock to {type(other)}:{other}")

        if fields is None:
            fields = self.fields

        diffs = []
        # Check if all the fields are equal
        for field in self.fields:
            a = getattr(self, field)
            b = getattr(other, field)
            # log.debug(f"Comparing field {field}: '{a}' <> '{b}'")
            if getattr(self, field) != getattr(other, field):
                diffs.append(field)
        return diffs

    def __eq__(self, other):
        diffs = self.compare_fields(other)
        if len(diffs) == 0:
            return True

    def __repr__(self):

        return f"<DomainBlock {self._asdict()}>"

    def copy(self):
        """Make a copy of this object and return it
        """
        retval = DomainBlock(**self._asdict())
        return retval

    def update(self, dict):
        """Update my kwargs
        """
        for key in dict:
            setattr(self, key, dict[key])

    def __iter__(self):
        """Be iterable"""
        keys = self.fields

        if getattr(self, 'id', False):
            keys.append('id')

        for k in keys:
            yield k

    def __getitem__(self, k, default=None):
        "Behave like a dict for getting values"
        if k not in self.all_fields:
            raise KeyError(f"Invalid key '{k}'")

        return getattr(self, k, default)

    def get(self, k, default=None):
        return self.__getitem__(k, default)