"""Report vulnerability Mixin.

Definition of the main method to process an entry from the KB - Knowledge Base,
and emit a vulnerability message.
"""
import dataclasses
import enum
from typing import Optional, List, Any, Union, Dict

from ostorlab.agent.kb import kb
from ostorlab.agent.mixins.protocols import emit
from ostorlab.assets import asset


class MetadataType(enum.Enum):
    """Vulnerability location metadata type."""
    FILE_PATH = enum.auto()
    CODE_LOCATION = enum.auto()
    URL = enum.auto()
    PORT = enum.auto()


@dataclasses.dataclass
class VulnerabilityLocationMetadata:
    """Vulnerability location metadata holding the type
     and the value of 'where' the vulnerability was detected in the asset """
    metadata_type: MetadataType
    value: str


@dataclasses.dataclass
class VulnerabilityLocation:
    """Vulnerability location used to attach a vulnerability to a specific asset"""
    metadata: List[VulnerabilityLocationMetadata]
    asset: asset.Asset

    def to_dict(self) -> Dict[str, Union[Any, List[Dict[str, str]]]]:
        """Convert data class to a dict matching what is expected from protobuf."""
        return {
            self.asset.proto_field: self.asset.__dict__,
            'metadata': [{'value': meta.value, 'type': meta.metadata_type.name} for meta in self.metadata]
        }


class RiskRating(enum.Enum):
    """Risk ratings enumeration."""
    HIGH = enum.auto()
    MEDIUM = enum.auto()
    LOW = enum.auto()
    POTENTIALLY = enum.auto()
    HARDENING = enum.auto()
    SECURE = enum.auto()
    IMPORTANT = enum.auto()
    INFO = enum.auto()


class AgentReportVulnMixin(emit.EmitProtocol):
    """Report Vulnerability class implementing logic of fetching entries from the knowledge base,
    and emitting vulnerability messages."""

    def report_vulnerability(self,
                             entry: kb.Entry,
                             technical_detail: str,
                             risk_rating: RiskRating,
                             dna: Optional[str] = None,
                             vulnerability_location: Optional[VulnerabilityLocation] = None) -> None:
        """Fetch the details of an entry from the knowledge base, and emit a vulnerability message.
        Args:
            entry: knowledge base entry.
            technical_detail: markdown of the scan results.
            risk_rating: the risk rating assigned to the result of the scan.
            dna: unique identifier for duplicate vulnerabilities.
            vulnerability_location: vulnerability location where the vulnerability was detected.
        Returns:
            None
        """
        title = entry.title
        short_description = entry.short_description
        description = entry.description
        recommendation = entry.recommendation
        security_issue = entry.security_issue
        privacy_issue = entry.privacy_issue
        has_public_exploit = entry.has_public_exploit
        targeted_by_malware = entry.targeted_by_malware
        targeted_by_ransomware = entry.targeted_by_ransomware
        targeted_by_nation_state = entry.targeted_by_nation_state
        cvss_v3_vector = entry.cvss_v3_vector

        references = []
        for key, value in entry.references.items():
            reference = {}
            reference['title'] = key
            reference['url'] = value
            references.append(reference)
        data = {
            'title': title,
            'technical_detail': technical_detail,
            'risk_rating': risk_rating.name,
            'short_description': short_description,
            'description': description,
            'recommendation': recommendation,
            'references': references,
            'security_issue': security_issue,
            'privacy_issue': privacy_issue,
            'has_public_exploit': has_public_exploit,
            'targeted_by_malware': targeted_by_malware,
            'targeted_by_ransomware': targeted_by_ransomware,
            'targeted_by_nation_state': targeted_by_nation_state,
            'cvss_v3_vector': cvss_v3_vector,
            'dna': dna
        }
        if vulnerability_location is not None:
            data['vulnerability_location'] = vulnerability_location.to_dict()

        # if dna is not specified, it should not be provided to the portal otherwise it will cause a problem.
        if dna is None:
            data.pop('dna')

        selector = 'v3.report.vulnerability'
        self.emit(selector, data)
