import json
from datetime import datetime
from unittest.mock import patch

import pytest

from company.models.company import Company
from company.models.location import LocationCode
from incident.models import Incident
from ncircc.enums.notifications import NotificationCategoryEnum, ImpactEffect, EventTypeEnum, AffectedSystemFunction, \
    TlpEnum, ActivityStatusEnum, AffectedSystemCategoryEnum, NotificationStatusEnum
from ncircc.models.notification import Notification
from ncircc.services.notification import NotificationSenderCreateService, NotificationUpdateSenderService, \
    NotificationGetterUpdatedStatusServices, NICRCCUpdateException
from ncircc.tests.utils import mocked_notification_requests_post_status_201, \
    mocked_notification_requests_post_status_400, mocked_notification_request_get_status_200


@pytest.mark.django_db
class TestNotificationSenderCreateService:
    """Test build data and send data to NCIRCC. Also saving data from NCITCC."""

    incident = None
    notification = None
    company = None

    @pytest.fixture(autouse=True)
    def setup_test(self):
        self.location, _ = LocationCode.objects.get_or_create(code='RU-MOS')
        self.company = Company.objects.create(name='Test_name', api_key='test_api_key',
                                              city='Moscow', location=self.location)
        self.incident = Incident.objects.create(title='test_inc_1', importance=10, event_count=10,
                                                events=[{'type': 'armaif_1',
                                                         'Index': 'aggregated-2022.03.23',
                                                         'sign_id': '63',
                                                         '@created': '2022-03-23T10:06:08.90934681Z',
                                                         'event_id': '81c5a5ec-7779-4c8d-a942-6e8642937b58',
                                                         'rule_tags': None,
                                                         'sign_name': 'Firewall Rule',
                                                         'source_ip': '127.0.0.1',
                                                         '@timestamp': '2022-03-23T10:06:06.697862758Z',
                                                         'event_hash': '2c34bfca72f29c21e0f136b2c53773979908a3978779978cc5c65d9ecfec28fa',
                                                         'event_last': '2022-03-23T10:06:06.703452398Z',
                                                         'source_mac': '',
                                                         'celery_done': False,
                                                         'event_count': 1,
                                                         'event_first': '2022-03-23T10:06:06.703452398Z',
                                                         'source_host': '',
                                                         'source_port': 46084,
                                                         'source_user': '',
                                                         'aggregated_id': '1036597098_2c34bfca72f29c21e0f136b2c53773979908a3978779978cc5c65d9ecfec28fa',
                                                         'device_action': 'разрешение (pass)',
                                                         'device_vendor': 'InfoWatch ARMA',
                                                         'event_src_msg': '<14>CEF:0|InfoWatch ARMA|ARMAIF|3.5|pfalert|PF rule alert|8|cs1=63 cs2=deviceInboundInterface=lo0 act=разрешение (pass) src=127.0.0.1 deviceDirection=in proto=icmp dst=127.0.0.1 spt=46084 dpt=53 rt=1648040766697 log_from=filterlog cid=None\n',
                                                         'sign_category': 'PF',
                                                         'destination_ip': '127.0.0.1',
                                                         'device_product': 'Industrial Firerwall',
                                                         'device_version': '3.5',
                                                         'event_protocol': 'icmp',
                                                         'event_severity': 8,
                                                         'event_timestamp': '2022-03-23T10:06:06.703452398Z',
                                                         'destination_host': '',
                                                         'destination_port': 53,
                                                         'destination_user': '',
                                                         'sign_subcategory': ''}])

        self.notification = Notification.objects.create(
            incident=self.incident,
            affected_system_name='affected_system_name_test',
            event_description='event_description_test',
            type=EventTypeEnum.DDOS.value,
        )

    @pytest.mark.unit
    def test_get_headers(self):
        """Test bulding headers for sending"""
        notification_sender = NotificationSenderCreateService(self.notification.pk)
        headers = notification_sender.get_headers()

        assert isinstance(headers, dict)
        assert 'x-token' in headers
        assert headers.get('x-token') == self.company.api_key
        assert 'Content-Type' in headers
        assert headers.get('Content-Type') == 'application/json'

    @pytest.mark.unit
    def test_building_payload_for_incident(self):
        """Test building data for category NotificationCategoryEnum.INCIDENT"""
        required_fields = {
            'city', 'location', 'affectedsystemfunction',
            'category', 'type', 'tlp', 'detecttime', 'eventdescription', 'activitystatus',
            'affectedsystemname', 'affectedsystemcategory', 'affectedsystemconnection', 'assistance',
            'integrityimpact', 'availabilityimpact', 'confidentialityimpact', 'customimpact', 'endtime',
            'detectiontool',
        }
        self.notification.category = NotificationCategoryEnum.INCIDENT.value
        self.notification.integrity_impact = ImpactEffect.NO.value
        self.notification.availability_impact = ImpactEffect.LOW.value
        self.notification.confidentiality_impact = ImpactEffect.HIGH.value
        self.notification.custom_impact = 'custom_incident_impact_test'
        self.notification.save()

        notification_sender = NotificationSenderCreateService(self.notification.pk)
        payload = notification_sender.get_payload()

        assert isinstance(payload, dict)
        for field in required_fields:
            assert field in payload

        assert payload['category'] == NotificationCategoryEnum.INCIDENT.value
        assert payload['type'] == 'DDoS-атака'
        assert payload['activitystatus'] == ActivityStatusEnum.TAKEN_ACTION.value
        assert payload['tlp'] == TlpEnum.GREEN.value
        assert payload['affectedsystemname'] == 'affected_system_name_test'
        assert payload['affectedsystemcategory'] == AffectedSystemCategoryEnum.RESOURCE_NOT_CII.value
        assert payload['eventdescription'] == 'event_description_test'
        assert not payload['affectedsystemconnection']
        assert not payload['assistance']
        assert payload['detecttime'] == self.incident.events[0].get('event_first', '').split('.')[0]
        assert payload['endtime'] == self.incident.events[-1].get('event_first', '').split('.')[0]

        assert payload['city'] == self.company.city
        assert payload['location'] == self.location.code
        assert payload['affectedsystemfunction'] == AffectedSystemFunction.NUCLEAR_POWER.value

        assert payload['integrityimpact'] == ImpactEffect.NO.value
        assert payload['availabilityimpact'] == ImpactEffect.LOW.value
        assert payload['confidentialityimpact'] == ImpactEffect.HIGH.value
        assert payload['customimpact'] == 'custom_incident_impact_test'
        assert payload['detectiontool'] == 'InfoWatch ARMA'

    @pytest.mark.unit
    def test_building_payload_attack(self):
        """Test building data for category NotificationCategoryEnum.ATTACK"""
        required_fields = {
            'city', 'location', 'affectedsystemfunction',
            'category', 'type', 'tlp', 'detecttime', 'eventdescription', 'activitystatus',
            'affectedsystemname', 'affectedsystemcategory', 'affectedsystemconnection', 'assistance',
            'integrityimpact', 'availabilityimpact', 'confidentialityimpact', 'customimpact',
        }
        self.notification.category = NotificationCategoryEnum.ATTACK.value
        self.notification.integrity_impact = ImpactEffect.NO.value
        self.notification.availability_impact = ImpactEffect.LOW.value
        self.notification.confidentiality_impact = ImpactEffect.HIGH.value
        self.notification.custom_impact = 'custom_attack_impact_test'
        self.notification.save()

        notification_sender = NotificationSenderCreateService(self.notification.pk)
        payload = notification_sender.get_payload()

        for field in required_fields:
            assert field in payload

    @pytest.mark.unit
    def test_building_payload_vulnerability(self):
        """Test building data for category NotificationCategoryEnum.VULNERABILITY"""
        self.notification.type = EventTypeEnum.VULNERABLE_RESOURCE
        self.notification.category = NotificationCategoryEnum.VULNERABILITY.value
        self.notification.vulnerability_id = 'CVE-2020-1231'
        self.notification.product_category = 'Microsoft'
        self.notification.save()

        required_fields = {
            'city', 'location', 'affectedsystemfunction',
            'category', 'type', 'tlp', 'detecttime', 'eventdescription', 'activitystatus',
            'affectedsystemname', 'affectedsystemcategory', 'affectedsystemconnection', 'assistance',
            'vulnerabilityid', 'productcategory',
        }

        notification_sender = NotificationSenderCreateService(self.notification.pk)
        payload = notification_sender.get_payload()

        for field in required_fields:
            assert field in payload

        assert payload['vulnerabilityid'] == 'CVE-2020-1231'
        assert payload['productcategory'] == 'Microsoft'

    @pytest.mark.unit
    @patch('requests.post', side_effect=mocked_notification_requests_post_status_201)
    def test_notification_sending_status_201(self, *args):
        self.notification.custom_impact = 'custom_incident_impact_test'
        self.notification.save()

        notification_sender = NotificationSenderCreateService(self.notification.pk)
        msg, status = notification_sender.send()
        assert status == 201
        assert msg == 'Success'

        notification = Notification.objects.get(pk=self.notification.pk)
        assert notification.uuid
        assert notification.update_time
        assert notification.identifier == '21-07-573'
        assert notification.sending_time

    @pytest.mark.unit
    @patch('requests.post', side_effect=mocked_notification_requests_post_status_400)
    def test_notification_status_400(self, *args):
        notification_sender = NotificationSenderCreateService(self.notification.pk)
        msg, status = notification_sender.send()
        assert status == 400
        assert msg == 'Передан неверный токен'

        notification = Notification.objects.get(pk=self.notification.pk)
        assert not notification.uuid
        assert not notification.update_time
        assert not notification.identifier
        assert not notification.sending_time


@pytest.mark.django_db
class TestNotificationSendUpdaterServices:

    @pytest.fixture(autouse=True)
    def setup_test(self):
        self.location, _ = LocationCode.objects.get_or_create(code='RU-MOS')
        self.company = Company.objects.create(name='Test_name', api_key='test_api_key',
                                              city='Moscow', location=self.location)
        self.incident = Incident.objects.create(title='test_inc_1', importance=10, event_count=10, events='')
        self.notification = Notification.objects.create(
            incident=self.incident,
            uuid='87e37777-0a7c-4879-888b-86f1d9cdb175',
            identifier='21-07-573',
            update_time=datetime.now(),
            affected_system_name='affected_system_name_test',
            event_description='event_description_test',
            type=EventTypeEnum.DDOS.value,
            notification_status=NotificationStatusEnum.ADDITION_REQUIRED.value,
        )

    @pytest.mark.unit
    def test_payload_for_incident(self):
        required_fields = {
            'uuid', 'status', 'eventdescription', 'integrityimpact', 'availabilityimpact',
            'confidentialityimpact', 'customimpact', 'city', 'activitystatus'
        }
        notification_updated = NotificationUpdateSenderService(self.notification.pk)
        data = notification_updated.get_payload()
        for field in required_fields:
            assert field in data

    @pytest.mark.unit
    def test_payload_for_attack(self):
        required_fields = {'uuid', 'status', 'eventdescription', 'city', 'activitystatus'}

        self.notification.category = NotificationCategoryEnum.ATTACK.value
        data = NotificationUpdateSenderService(self.notification.pk).get_payload()
        for field in required_fields:
            assert field in data

    @pytest.mark.unit
    def test_payload_for_vulnerability(self):
        required_fields = {'uuid', 'status', 'eventdescription', 'city', 'activitystatus'}
        self.notification.category = NotificationCategoryEnum.VULNERABILITY.value
        data = NotificationUpdateSenderService(self.notification.pk).get_payload()
        for field in required_fields:
            assert field in data

    @pytest.mark.unit
    def test_raise_error_in_init_(self):
        """Test to Updating to NCIRRCC. Updating is possible with status `ADDITION_REQUIRED`"""
        self.notification.notification_status = NotificationStatusEnum.ARCHIVED.value
        self.notification.save()
        with pytest.raises(NICRCCUpdateException):
            NotificationUpdateSenderService(self.notification.pk)


@pytest.mark.django_db
class TestNotificationGetUpdatedDataFromNCIRCC:

    @pytest.fixture(autouse=True)
    def setup_test(self):
        self.location, _ = LocationCode.objects.get_or_create(code='RU-MOS')
        self.company = Company.objects.create(name='Test_name', api_key='test_api_key',
                                              city='Moscow', location=self.location)
        self.incident = Incident.objects.create(title='test_inc_1', importance=10, event_count=10, events='')
        self.notification = Notification.objects.create(
            incident=self.incident,
            uuid='87e37777-0a7c-4879-888b-86f1d9cdb175',
            identifier='21-07-573',
            update_time=datetime.now(),
            affected_system_name='affected_system_name_test',
            event_description='event_description_test',
            type=EventTypeEnum.DDOS.value,
        )

    @pytest.mark.unit
    @patch('requests.get', side_effect=mocked_notification_request_get_status_200)
    def test_getting_data_from_ncircc(self, *args):
        getter = NotificationGetterUpdatedStatusServices(self.notification.pk)
        assert self.notification.notification_status == NotificationStatusEnum.CHECK_NCIRCC.value
        msg, status = getter.send()
        assert status == 200
        assert not self.notification.notification_status == NotificationStatusEnum.ARCHIVED.value

    @pytest.mark.unit
    def test_get_params_method(self):
        data_filter = json.dumps([{
            'property': 'uuid',
            'operator': '=',
            'value': str(self.notification.uuid)
        }])
        sender = NotificationGetterUpdatedStatusServices(self.notification.pk)
        params = sender.get_params()
        assert isinstance(params, tuple)
        assert params[0][1] == data_filter
        assert 'status' in params[1][1]
        assert 'updated' in params[1][1]
