"""Testing events splitter

SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
SPDX-License-Identifier: AGPL-3.0-or-later
"""

import json
import os
import unittest
from unittest import mock

import intelmq.lib.test as test
import requests
from requests_mock import MockerCore

from intelmq_extensions.bots.experts.vulnerability_lookup.expert import (
    VulnerabilityLookupExpertBot,
)

from ....base import BotTestCase

INPUT = {
    "__type": "Event",
    "classification.identifier": "zeus",
    "classification.type": "vulnerable-system",
    "notify": True,
}


@test.skip_redis()
class TestVulnerabilityLookupExpertBot(BotTestCase, unittest.TestCase):
    @classmethod
    def set_bot(cls):
        cls.use_cache = True
        cls.bot_reference = VulnerabilityLookupExpertBot

    def mock_http_session(self):
        session = requests.Session()
        session_mock = mock.patch(
            "intelmq_extensions.bots.experts.vulnerability_lookup.expert.create_request_session",
            return_value=session,
        )
        session_mock.start()
        self.addCleanup(session_mock.stop)

        self.requests = MockerCore(session=session)
        self.requests.start()
        self.addCleanup(self.requests.stop)

    def _load_data_file(self, filename: str):
        current_dir = os.path.dirname(os.path.realpath(__file__))
        file_path = os.path.join(current_dir, "data", filename)
        with open(file_path, "r") as f:
            return json.load(f)

    def mock_request(
        self,
        path: str,
        text: str = None,
        json_: dict = None,
        data_file: str = None,
        **kwargs,
    ):
        if text is not None:
            kwargs["text"] = text
        elif json_ is not None:
            kwargs["json"] = json_
        else:
            kwargs["json"] = self._load_data_file(data_file)
        self.requests.get(
            f"https://vulnerability.circl.lu/api/{path}",
            status_code=200,
            **kwargs,
        )

    def setUp(self):
        super().setUp()
        self.mock_http_session()

    def tearDown(self):
        self.cache.flushdb()
        return super().tearDown()

    def test_not_existing_vuln(self):
        self.mock_request("vulnerability/zeus", "null\n")

        self.input_message = INPUT
        self.run_bot()

        self.assertMessageEqual(0, INPUT)
        self.assertEqual(1, self.requests.call_count)

    def test_filter_classification(self):
        self.input_message = {**INPUT, "classification.type": "information-disclosure"}
        self.run_bot()

        self.assertMessageEqual(
            0, {**INPUT, "classification.type": "information-disclosure"}
        )
        self.assertEqual(0, self.requests.call_count)

    def test_no_epss(self):
        self.mock_request(
            "vulnerability/cve-2023-49606", data_file="CVE-2023-49606.json"
        )
        self.mock_request("epss/cve-2023-49606", json_={"data": []})

        self.input_message = {**INPUT, "classification.identifier": "CVE-2023-49606"}
        self.run_bot()

        expected = {
            **INPUT,
            "classification.identifier": "CVE-2023-49606",
            "event_description.url": "https://vulnerability.circl.lu/vuln/cve-2023-49606",
            "event_description.text": "A use-after-free vulnerability exists in the HTTP Connection Headers parsing in Tinyproxy 1.11.1 and Tinyproxy 1.10.0. A specially crafted HTTP header can trigger reuse of previously freed memory, which leads to memory corruption and could lead to remote code execution. An attacker needs to make an unauthenticated HTTP request to trigger this vulnerability.",  # noqa: E501
            "extra.cvss3_1": 9.8,
        }
        self.assertEqual(2, self.requests.call_count)
        self.assertMessageEqual(0, expected)

    def test_cve_with_epss_and_cut_description(self):
        self.mock_request(
            "vulnerability/CVE-2024-23113", data_file="CVE-2024-23113.json"
        )
        self.mock_request("epss/CVE-2024-23113", data_file="CVE-2024-23113_epss.json")

        self.input_message = {**INPUT, "classification.identifier": "CVE-2024-23113"}
        self.run_bot(parameters={"description_length": 100})

        expected = {
            **INPUT,
            "classification.identifier": "CVE-2024-23113",
            "event_description.url": "https://vulnerability.circl.lu/vuln/cve-2024-23113",
            "event_description.text": "A use of externally-controlled format string in Fortinet FortiOS versions 7.4.0 through 7.4.2, 7.2.0...",  # noqa: E501
            "extra.cvss3_1": 9.8,
            "extra.epss": "0.416590000",
        }
        self.assertEqual(2, self.requests.call_count)
        self.assertMessageEqual(0, expected)

    def test_cache_is_used(self):
        self.mock_request(
            "vulnerability/CVE-2024-23113", data_file="CVE-2024-23113.json"
        )
        self.mock_request("epss/CVE-2024-23113", data_file="CVE-2024-23113_epss.json")

        self.input_message = [
            {**INPUT, "classification.identifier": "CVE-2024-23113"},
            {**INPUT, "classification.identifier": "CVE-2024-23113"},
        ]
        self.run_bot(parameters={"description_length": 100}, iterations=2)

        expected = {
            **INPUT,
            "classification.identifier": "CVE-2024-23113",
            "event_description.url": "https://vulnerability.circl.lu/vuln/cve-2024-23113",
            "event_description.text": "A use of externally-controlled format string in Fortinet FortiOS versions 7.4.0 through 7.4.2, 7.2.0...",  # noqa: E501
            "extra.cvss3_1": 9.8,
            "extra.epss": "0.416590000",
        }
        self.assertEqual(2, self.requests.call_count)
        self.assertMessageEqual(0, expected)
        self.assertMessageEqual(1, expected)

    def test_multiple_cvss_and_description_with_new_lines(self):
        self.mock_request("vulnerability/cve-2025-2742", data_file="CVE-2025-2742.json")
        self.mock_request("epss/cve-2025-2742", json_={"data": []})

        self.input_message = {**INPUT, "classification.identifier": "CVE-2025-2742"}
        self.run_bot(parameters={"description_length": 100})

        expected = {
            **INPUT,
            "classification.identifier": "CVE-2025-2742",
            "event_description.url": "https://vulnerability.circl.lu/vuln/cve-2025-2742",
            "event_description.text": "A vulnerability classified     as critical was found in zhijiantianya ruoyi-vue-pro 2.4.1. This vuln...",  # noqa: E501
            "extra.cvss3_0": 5.4,
            "extra.cvss3_1": 5.4,
            "extra.cvss4_0": 5.3,
        }
        self.assertEqual(2, self.requests.call_count)
        self.assertMessageEqual(0, expected)

    def test_ghsa(self):
        self.mock_request(
            "vulnerability/ghsa-r6xg-mjqp-qqg4", data_file="GHSA-r6xg-mjqp-qqg4.json"
        )
        self.mock_request("epss/ghsa-r6xg-mjqp-qqg4", json_={"data": []})

        self.input_message = {
            **INPUT,
            "classification.identifier": "GHSA-r6xg-mjqp-qqg4",
        }
        self.run_bot(parameters={"description_length": 100})

        expected = {
            **INPUT,
            "classification.identifier": "GHSA-r6xg-mjqp-qqg4",
            "event_description.url": "https://vulnerability.circl.lu/vuln/ghsa-r6xg-mjqp-qqg4",
            "event_description.text": "A vulnerability classified as critical was found in zhijiantianya ruoyi-vue-pro 2.4.1. This vulnerab...",  # noqa: E501
        }
        self.assertEqual(2, self.requests.call_count)
        self.assertMessageEqual(0, expected)
