# SPDX-License-Identifier: MPL-2.0
# -*- coding: utf-8 -*-

import abc

import attr

from cryptodatahub.common.algorithm import BlockCipher, KeyExchange
from cryptodatahub.common.grade import AttackNamed, Grade, GradeableSimple
from cryptodatahub.common.types import convert_value_to_object

from cryptoparser.common.base import Serializable


@attr.s
class VulnerabilityResult(Serializable, GradeableSimple):
    value = attr.ib(validator=attr.validators.instance_of(bool))

    @property
    @abc.abstractmethod
    def grade(self):
        raise NotImplementedError()

    def __str__(self):
        return self._markdown_result(self.value)[1]

    @classmethod
    @abc.abstractmethod
    def get_name(cls):
        raise NotImplementedError()


@attr.s
class VulnerabilityResultGraded(VulnerabilityResult):
    @property
    def grade(self):
        return self._vulnerable_grade if self.value else Grade.SECURE

    @classmethod
    @abc.abstractmethod
    def get_name(cls):
        raise NotImplementedError()

    @property
    @abc.abstractmethod
    def _vulnerable_grade(self):
        raise NotImplementedError()


@attr.s
class VulnerabilityResultAnonymousDH(VulnerabilityResultGraded):
    @property
    def _vulnerable_grade(self):
        return Grade.INSECURE

    @classmethod
    def get_name(cls):
        return 'Anonymous Diffie-Hellman'


@attr.s
class VulnerabilityResultNullEncryption(VulnerabilityResultGraded):
    @property
    def _vulnerable_grade(self):
        return Grade.INSECURE

    @classmethod
    def get_name(cls):
        return 'NULL encryption'


@attr.s
class VulnerabilityResultRC4(VulnerabilityResultGraded):
    @property
    def _vulnerable_grade(self):
        return Grade.INSECURE

    @classmethod
    def get_name(cls):
        return 'RC4'


class VulnerabilityResultAttackNamed(VulnerabilityResult):
    @property
    def grade(self):
        return self.get_attack_named().value.grade if self.value else Grade.SECURE

    @classmethod
    def get_name(cls):
        return cls.get_attack_named().value.name

    @classmethod
    @abc.abstractmethod
    def get_attack_named(cls):
        raise NotImplementedError()


class VulnerabilityResultDheat(VulnerabilityResultAttackNamed):
    @classmethod
    def get_attack_named(cls):
        return AttackNamed.DHEAT_ATTACK


class VulnerabilityResultNonForwardSecret(VulnerabilityResultAttackNamed):
    @classmethod
    def get_attack_named(cls):
        return AttackNamed.NOFS


class VulnerabilityResultSweet32(VulnerabilityResultAttackNamed):
    @classmethod
    def get_attack_named(cls):
        return AttackNamed.SWEET32


class VulnerabilityResultWeakDh(VulnerabilityResultAttackNamed):
    @classmethod
    def get_attack_named(cls):
        return AttackNamed.WEAK_DH


@attr.s
class AnalyzerResultVulnerabilityCiphersBase():  # pylint: disable=too-many-instance-attributes
    """
    :class: Vulnerabilities relate to cryptographic algorithms. Any attribute represents an vulnerability, which value
        is true if any of the negotiable cryptographic algorithm uses an algorithm affected by the vulnerability.

    :param sweet32: `Sweet32 attack <https://sweet32.info/>`__.
    :param anonymous_dh:
        `Anonymous Diffie-Hellman <https://en.wikipedia.org/wiki/Key-agreement_protocol#Exponential_key_exchange>`__ key
        exchange algorithm.
    :param null_encryption: Cipher suite does use `no/null null <https://en.wikipedia.org/wiki/Null_encryption>`__
        encryption
    :param rc4: Cipher suite uses `RC4 stream ciphers <https://en.wikipedia.org/wiki/RC4#Security>`__.
    :param non_forward_secret: Cipher suite uses key exchange algorithm which does not provide
        `forward secrecy <https://en.wikipedia.org/wiki/Forward_secrecy>`__
    """

    _ANONYMOUS_DH_ALGORITHMS = set([KeyExchange.ADH, KeyExchange.AECDH])
    _RC4_ALGORITHMS = set([
        BlockCipher.RC4_40, BlockCipher.RC4_56, BlockCipher.RC4_64, BlockCipher.RC4_128, BlockCipher.RC4_256
    ])
    _SWEET32_ALGORITHMS = set(filter(
        lambda bulk_cipher: any(map(
            lambda vulnerability: vulnerability.named == AttackNamed.SWEET32,
            [] if bulk_cipher.value.vulnerabilities is None else bulk_cipher.value.vulnerabilities
        )),
        BlockCipher
    ))

    sweet32 = attr.ib(
        converter=convert_value_to_object(VulnerabilityResultSweet32),
        validator=attr.validators.instance_of(VulnerabilityResultSweet32),
        metadata={'human_readable_name': VulnerabilityResultSweet32.get_name()},
    )
    anonymous_dh = attr.ib(
        converter=convert_value_to_object(VulnerabilityResultAnonymousDH),
        validator=attr.validators.instance_of(VulnerabilityResultAnonymousDH),
        metadata={'human_readable_name': VulnerabilityResultAnonymousDH.get_name()},
    )
    rc4 = attr.ib(
        converter=convert_value_to_object(VulnerabilityResultRC4),
        validator=attr.validators.instance_of(VulnerabilityResultRC4),
        metadata={'human_readable_name': VulnerabilityResultRC4.get_name()},
    )
    non_forward_secret = attr.ib(
        converter=convert_value_to_object(VulnerabilityResultNonForwardSecret),
        validator=attr.validators.instance_of(VulnerabilityResultNonForwardSecret),
        metadata={'human_readable_name': VulnerabilityResultNonForwardSecret.get_name()},
    )

    @classmethod
    def from_algorithms(cls, key_exchange_algorithms, bulk_cipher_algorithms):
        sweet32 = VulnerabilityResultSweet32(
            bool(cls._SWEET32_ALGORITHMS & set(bulk_cipher_algorithms))
        )
        anonymous_dh = VulnerabilityResultAnonymousDH(
            bool(cls._ANONYMOUS_DH_ALGORITHMS & set(key_exchange_algorithms))
        )
        rc4 = VulnerabilityResultRC4(
            bool(cls._RC4_ALGORITHMS & set(bulk_cipher_algorithms))
        )
        non_forward_secret = VulnerabilityResultNonForwardSecret(any(map(
            lambda key_exchange_algorithm: not key_exchange_algorithm.value.forward_secret, key_exchange_algorithms
        )))

        return cls(sweet32=sweet32, anonymous_dh=anonymous_dh, rc4=rc4, non_forward_secret=non_forward_secret)


@attr.s
class AnalyzerResultVulnerabilityDHParamsBase():
    """
    :class: Vulnerabilities relate to the protocol versions. Any attribute represents a vulnerability, which value is
        true if any of the negotiable protocol versions uses an algorithm affected by the vulnerability.

    :param dheat: `D(HE)at attack <https://dheatattack.gitlab.io/>`__.
    """

    dheat = attr.ib(
        converter=convert_value_to_object(VulnerabilityResultDheat),
        validator=attr.validators.instance_of(VulnerabilityResultDheat),
        metadata={'human_readable_name': VulnerabilityResultDheat.get_name()},
    )
    weak_dh = attr.ib(
        converter=convert_value_to_object(VulnerabilityResultWeakDh),
        validator=attr.validators.instance_of(VulnerabilityResultWeakDh),
        metadata={'human_readable_name': VulnerabilityResultWeakDh.get_name()},
    )

    @classmethod
    def from_key_sizes(cls, key_sizes):
        weak_dh = VulnerabilityResultWeakDh(any(map(lambda key_size: key_size <= 1024, key_sizes)))
        dheat = VulnerabilityResultDheat(any(map(lambda key_size: key_size > 4096, key_sizes)))

        return cls(weak_dh=weak_dh, dheat=dheat)


@attr.s
class AnalyzerResultVulnerabilityVersionsBase():
    """
    :class: Vulnerabilities relate to the protocol versions. Any attribute represents a vulnerability, which value is
        true if any of the negotiable protocol versions uses an algorithm affected by the vulnerability.
    """
