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

import ipaddress
import typing

import attr

from cryptoparser.common.base import Serializable
from cryptoparser.httpx.version import HttpVersion
from cryptoparser.ike.version import IsakmpProtocolVersion
from cryptoparser.ssh.version import SshProtocolVersion
from cryptoparser.tls.version import TlsProtocolVersion
from cryptoparser.ike.version import IsakmpVersion

from cryptolyzer.common.transfer import L7TransferBase


@attr.s
class AnalyzerTargetBase(Serializable):
    scheme = attr.ib(validator=attr.validators.instance_of(str))
    address = attr.ib(validator=attr.validators.instance_of(str))

    @classmethod
    def _from_l7_client(cls, l7_client, **kwargs):
        raise NotImplementedError()

    @classmethod
    def from_l7_client(cls, l7_client, proto_version=None):
        raise NotImplementedError()


@attr.s
class AnalyzerTarget(AnalyzerTargetBase):
    ip: typing.Union[str, ipaddress.IPv4Address, ipaddress.IPv6Address] = attr.ib(
        validator=attr.validators.instance_of((
            str, ipaddress.IPv4Address, ipaddress.IPv6Address
        )),
        metadata={'human_readable_name': 'IP address'}
    )
    port = attr.ib(validator=attr.validators.instance_of(int))

    @classmethod
    def _from_l7_client(cls, l7_client, **kwargs):
        return cls(l7_client.get_scheme(), l7_client.address, l7_client.ip, l7_client.port, **kwargs)

    @classmethod
    def from_l7_client(cls, l7_client, proto_version=None):
        return cls._from_l7_client(l7_client, proto_version=proto_version)


@attr.s
class AnalyzerTargetHttp(AnalyzerTarget):
    path = attr.ib(
        default=None,
        validator=attr.validators.optional(attr.validators.instance_of(str)),
    )
    proto_version = attr.ib(
        default=HttpVersion.HTTP1_1,
        validator=attr.validators.optional(attr.validators.in_(HttpVersion)),
        metadata={'human_readable_name': 'Protocol Version'}
    )

    @classmethod
    def _from_l7_client(cls, l7_client, **kwargs):
        if l7_client.uri.port is None:
            port = l7_client.get_default_port()
        else:
            port = int(l7_client.uri.port)

        return cls(l7_client.get_scheme(), l7_client.uri.host, '', port, path=l7_client.uri.path)


@attr.s
class AnalyzerTargetIke(AnalyzerTarget):
    proto_version: IsakmpProtocolVersion = attr.ib(
        default=None,
        validator=attr.validators.optional(attr.validators.instance_of(IsakmpProtocolVersion)),
        metadata={'human_readable_name': 'Protocol Version'}
    )


@attr.s
class AnalyzerTargetTls(AnalyzerTarget):
    proto_version = attr.ib(
        default=None,
        validator=attr.validators.optional(attr.validators.instance_of(TlsProtocolVersion)),
        metadata={'human_readable_name': 'Protocol Version'}
    )


@attr.s
class AnalyzerTargetSsh(AnalyzerTarget):
    proto_version = attr.ib(
        default=None,
        validator=attr.validators.optional(attr.validators.instance_of(SshProtocolVersion)),
        metadata={'human_readable_name': 'Protocol Version'}
    )


@attr.s
class AnalyzerTargetDnsRecord(AnalyzerTargetBase):
    server = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(str)))

    @classmethod
    def _from_l7_client(cls, l7_client, **kwargs):
        return cls(l7_client.get_scheme(), l7_client.domain.host, l7_client.domain.fragment)

    @classmethod
    def from_l7_client(cls, l7_client, proto_version=None):
        return cls._from_l7_client(l7_client)


@attr.s
class AnalyzerTargetIKE(AnalyzerTarget):
    proto_version = attr.ib(
        default=None,
        validator=attr.validators.optional(attr.validators.instance_of(IsakmpVersion)),
        metadata={'human_readable_name': 'Protocol Version'}
    )


@attr.s
class AnalyzerResultBase(Serializable):
    target: typing.Union[AnalyzerTargetBase, L7TransferBase] = attr.ib(
        validator=attr.validators.instance_of((AnalyzerTargetBase, L7TransferBase))
    )

    def _as_markdown_without_target(self, value, level):
        multiline, attr_result = self._markdown_result(value, level)
        if not multiline:
            attr_result += '\n'

        if value is not None and isinstance(value, AnalyzerResultBase):
            target_result_line_count = len(self._markdown_result(value.target, level)[1].split('\n'))
            attr_result = '\n'.join(attr_result.split('\n')[target_result_line_count:])

        return attr_result


@attr.s
class AnalyzerResultError(AnalyzerResultBase):
    target: typing.Union[AnalyzerTargetBase, L7TransferBase, str] = attr.ib(
        validator=attr.validators.instance_of((AnalyzerTargetBase, L7TransferBase, str))
    )
    error = attr.ib(validator=attr.validators.instance_of(str))


@attr.s
class AnalyzerResultAllBase(AnalyzerResultBase):
    def _as_markdown(self, level):
        result = ''

        dict_value = self._asdict()
        name_dict = self._markdown_human_readable_names(self, dict_value)
        for attr_name, value in dict_value.items():
            header_signs = (level + 1) * '#'
            result += f'{header_signs} {name_dict[attr_name]}\n\n'
            if (value is None or isinstance(value, (AnalyzerResultBase, AnalyzerTarget))):
                result += self._as_markdown_without_target(value, level)
            else:
                for index, cipher_result in enumerate(value):
                    if index:
                        result += '\n'

                    header_signs = (level + 2) * '#'
                    result += f'{header_signs} {cipher_result.target.proto_version}\n\n'
                    result += self._as_markdown_without_target(cipher_result, level)
            result += '\n'

        return True, result


@attr.s
class AnalyzerResultTls(AnalyzerResultBase):
    pass


@attr.s
class AnalyzerResultSsh(AnalyzerResultBase):
    pass


@attr.s
class AnalyzerResultHttp(AnalyzerResultBase):
    pass


@attr.s
class AnalyzerResultDnsRecord(AnalyzerResultBase):
    pass


@attr.s
class AnalyzerResultIKE(AnalyzerResultBase):
    """Base class for IKE analyzer results."""
