import datetime
import logging
from unittest.mock import patch

import pytest
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from rest_framework import status

from correlation.constants import Type
from correlation.models import Rule, Group

_log = logging.getLogger(__name__)


def mock_correlator_task(*args, **kwargs):
    pass


@pytest.mark.django_db(transaction=True)
class TestRuleOrdering:

    @pytest.fixture(autouse=True)
    def setup_test(self, add_user_with_permissions):
        """Create two rules with different status."""
        self.user = add_user_with_permissions(username='superuser', password='pwd123', is_superuser=True)

        Rule.objects.all().delete()
        rule_type = Type.system
        rule_data = {
            'name': '1 Test with Status True',
            'type': rule_type,
            'status': True,
            'rev': 1,
            'sid': 2,
            'depth': datetime.timedelta(minutes=10),
            'rule_json': {
                "type": "query_string",
                "field": "",
                "operands": "event_severity:>=6",
            },
            'actions_json': {
                "type": "incident",
                "title": "{{.SignName}}",
                "comment": "",
                "category": "",
                "importance": "50",
                "assigned_to": "",
                "description": "{{.EventSrcMsg}}"
            }
        }
        self.rule1 = Rule.objects.create(
            **rule_data
        )

        rule_data_disable = rule_data
        rule_data_disable['status'] = False
        rule_data_disable['rev'] = 2
        self.rule1_disable = Rule.objects.create(**rule_data_disable)

        self.rule2 = Rule.objects.create(
            name='2 Test with Status False',
            type=rule_type,
            status=False,
            rev=1,
            sid=3,
            depth=datetime.timedelta(minutes=10),
            rule_json={
                "type": "query_string",
                "field": "",
                "operands": "event_severity:>=6",
            },
            actions_json={
                "type": "incident",
                "title": "{{.SignName}}",
                "comment": "",
                "category": "",
                "importance": "50",
                "assigned_to": "",
                "description": "{{.EventSrcMsg}}"
            },

        )
        self.rule2.status = False
        self.rule2.save()

    @pytest.mark.integration
    def test_rules_list(self, client, add_user_with_permissions):
        user = add_user_with_permissions(username='test_user1', password='pwd123', is_superuser=True)
        client.force_login(user=user)
        url = reverse('rules-list')
        response = client.get(url)
        data_json = response.json()
        assert response.status_code == 200
        assert len(data_json) == 4

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_add_rule(self, client, add_user_with_permissions):
        user = add_user_with_permissions(username='test_user1', password='pwd123', is_superuser=True)
        client.force_login(user=user)
        url = reverse('rules-list')
        data = {
            'name': 'New Test with Status False',
            'sid': 3,
            'rev': 4,
            'kind': 'System',
            'group': None,
            'status': False,
            'multi': False,
            'archived': False,
            'type': 0,
            'depth': '00:20:00',
            'rule_json': {
                'type': 'query_string',
                'field': 'query field',
                'operands': 'event_severity:>=666'
            },
            'actions_json': {
                'type': 'incident',
                'title': '{{.SignName}}',
                'comment': 'New comment',
                'category': 'New category',
                'importance': '50',
                'assigned_to': '',
                'description': '{{.EventSrcMsg}}'
            }
        }
        response = client.post(url, data=data, content_type='application/json')
        assert response.status_code == 201
        rule = Rule.objects.get(name='New Test with Status False')
        assert rule.sid == 3
        assert rule.rev == 4
        assert rule.group == None
        assert not rule.status
        assert not rule.archived
        assert rule.type == 0
        assert rule.depth == datetime.timedelta(seconds=1200)
        assert rule.rule_json == {
            'type': 'query_string',
            'field': 'NULL',
            'operands': 'event_severity:>=666'
        }
        assert rule.actions_json == {
            'type': 'incident',
            'title': '{{.SignName}}',
            'comment': 'New comment',
            'category': 'New category',
            'importance': '50',
            'assigned_to': '',
            'description': '{{.EventSrcMsg}}'
        }

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_delete_rule(self, client, add_user_with_permissions):
        user = add_user_with_permissions(username='test_user1', password='pwd123', is_superuser=True)
        client.force_login(user=user)
        rule = Rule.objects.get(name='2 Test with Status False')
        rules_before = Rule.objects.count()
        url = reverse('rules-detail', kwargs={"pk": rule.pk})
        response = client.delete(url)
        assert response.status_code == 204
        assert Rule.objects.count() == rules_before - 1

    @pytest.mark.unit
    @pytest.mark.skip(reason='1.5 waiting of products')
    def test_edit_rule(self, client, add_user_with_permissions):
        user = add_user_with_permissions(username='test_user1', password='pwd123', is_superuser=True)
        client.force_login(user=user)
        rule = Rule.objects.get(name='2 Test with Status False')
        url = reverse('rules-detail', kwargs={"pk": rule.pk})
        data = {'name': '2 Test with Status False', 'sid': 5, 'rev': 6, 'kind': 'System', 'group': 'None',
                'status': True, 'multi': True,
                'archived': True, 'type': 1, 'depth': '00:20:00',
                'rule_json': {'type': 'query_string', 'field': 'field', 'operands': 'event_severity:>=50'},
                'actions_json': {'type': 'incident', 'title': '{{.SignName}}', 'comment': 'new comment', 'category': '',
                                 'importance': '500', 'assigned_to': 'user', 'description': '{{.EventSrcMsg}}'}}
        response = client.patch(url, data=data, content_type='application/json')
        assert response.status_code == 200
        rule = Rule.objects.get(name='2 Test with Status False')
        assert rule.sid == 5
        assert rule.rev == 7
        assert rule.type == 1
        assert not rule.group
        assert not rule.status
        assert rule.multi
        assert rule.archived
        assert rule.depth == datetime.timedelta(seconds=1200)
        assert rule.rule_json == {'type': 'query_string', 'field': 'field', 'operands': 'event_severity:>=50'}
        assert rule.actions_json == {'type': 'incident', 'title': '{{.SignName}}', 'comment': 'new comment',
                                     'category': '', 'importance': '500', 'assigned_to': 'user',
                                     'description': '{{.EventSrcMsg}}'}

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_enable_rule_but_this_sid_already_enable(self, api_client):
        api_client.force_login(user=self.user)

        url = reverse('rules-detail', kwargs={"pk": self.rule1_disable.pk})
        response = api_client.patch(url, data={'status': True})
        assert 'status' in response.json()
        assert response.json()['status'] == ['There is already an enabled rule with this sid: 2']

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_edit_rule_but_rule_with_same_sid_disable(self, api_client):
        api_client.force_login(user=self.user)

        url = reverse('rules-detail', kwargs={"pk": self.rule1.pk})
        response = api_client.patch(url, data={'description': '-----'})
        assert 'sid' in response.json()
        assert response.json()['sid'] == ['Check that there is no rule with SID 2 and REV 2 and try again']

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_expansion_rule_json_when_edit(self, api_client):
        api_client.force_login(user=self.user)

        url = reverse('rules-detail', kwargs={"pk": self.rule2.pk})
        response = api_client.patch(url, data={'rule_json': {'operands': 'test'}}, format='json')
        assert response.json()['rule_json'] == {
            'operands': 'test',
            'field': 'NULL',
            'type': 'query_string'
        }

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_edit_rule_only_status(self, api_client):
        api_client.force_login(user=self.user)

        url = reverse('rules-detail', kwargs={"pk": self.rule2.pk})
        response = api_client.patch(url, data={'status': True})
        assert response.json()['rev'] == 1
        assert Rule.objects.first().rev == 1

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_add_rule_error(self, client, add_user_with_permissions):
        user = add_user_with_permissions(username='test_user1', password='pwd123', is_superuser=True)
        client.force_login(user=user)
        url = reverse('rules-list')
        data = {
            'name': 'New Test with Status False',
            'sid': 3,
            'rev': 4,
            'kind': 'System',
            'group': 'None',
            'status': False,
            'multi': False,
            'archived': False,
            'type': 0,
            'depth': '00:20:00',
            'rule_json': {
                'type': 'query_string',
                'field': 'query field',
                'operands': 'event_severity:>=666'
            },

        }
        response = client.post(url, data=data, content_type='application/json')
        assert response.status_code == 400

    @pytest.mark.integration
    @patch('correlation.tasks.update_correlator_tasks', mock_correlator_task)
    @patch('correlation.api.update_correlator_tasks', mock_correlator_task)
    def test_add_rule_with_non_unique_sid(self, api_client, add_user_with_permissions):
        user = add_user_with_permissions(username='test_user1', password='pwd123', is_superuser=True)
        api_client.force_authenticate(user)
        url = reverse('rules-list')
        data = {
            'name': 'New Test',
            'sid': 3,
            'rev': 4,
            'kind': 'System',
            'group': None,
            'status': True,
            'multi': False,
            'archived': False,
            'type': 0,
            'depth': '00:20:00',
            'rule_json': {
                'type': 'query_string',
                'field': 'query field',
                'operands': 'event_severity:>=666'
            },
            'actions_json': {
                'type': 'incident',
                'title': '{{.SignName}}',
                'comment': 'New comment',
                'category': 'New category',
                'importance': '50',
                'assigned_to': '',
                'description': '{{.EventSrcMsg}}'
            }
        }

        response = api_client.post(url, data=data, format='json')
        assert response.status_code == status.HTTP_201_CREATED

        data['rev'] = 111111
        response = api_client.post(url, data=data, format='json')

        assert response.status_code == 400
        assert 'sid' in response.json()


@pytest.mark.django_db
class TestCorrelationGroups:

    @pytest.fixture(autouse=True)
    def setup_test(self, django_user_model):
        self.admin = django_user_model.objects.first()
        test_rule = dict(
            name='Rule 1',
            type=1,
            status=True,
            rev=1,
            sid=2,
            depth=datetime.timedelta(minutes=10),
            rule_json={
                "type": "query_string",
                "field": "",
                "operands": "event_severity:>=6",
            },
            actions_json={
                "type": "incident",
                "title": "{{.SignName}}",
                "comment": "",
                "category": "",
                "importance": "50",
                "assigned_to": "",
                "description": "{{.EventSrcMsg}}"
            },

        )
        self.rule1 = Rule.objects.create(**test_rule)
        self.group = Group.objects.create(name="test_group", description="test description")

    @pytest.mark.unit
    def test_get_group(self, api_client):
        api_client.force_authenticate(self.admin)
        response = api_client.get(reverse('rules-groups-list'))
        assert response.status_code == 200

        data_json = response.json()
        assert len(data_json['results']) == 1  # there is one group
        assert data_json['count'] == 1
        print(data_json)
        assert data_json['results'][0] == {
            'id': self.group.pk,
            'name': 'test_group',
            'description': 'test description',
            'rules': []
        }

    @pytest.mark.unit
    def test_add_group(self, api_client):
        api_client.force_authenticate(self.admin)
        groups_before = Group.objects.count()

        data = {
            "name": "New group",
            "description": "Group description"
        }
        response = api_client.post(reverse('rules-groups-list'), data=data)
        assert response.status_code == 201
        assert response.json()['name'] == data['name']
        assert response.json()['description'] == data['description']

        groups_after = Group.objects.count()
        assert groups_before != groups_after

        group = Group.objects.get(name="New group")
        assert group.description == "Group description"

    @pytest.mark.unit
    def test_delete_group(self, api_client):
        api_client.force_authenticate(self.admin)

        group = Group.objects.create(name="group", description="test description")
        url = reverse('rules-groups-detail', kwargs={"pk": group.pk})
        response = api_client.delete(url)
        assert response.status_code == 204
        with pytest.raises(ObjectDoesNotExist):
            Group.objects.get(pk=group.pk)

    @pytest.mark.unit
    def test_update_group(self, api_client):
        api_client.force_authenticate(self.admin)
        group = Group.objects.create(name="group", description="test description")
        data = {
            "name": "New group",
            "description": "Group description"
        }
        url = reverse('rules-groups-detail', kwargs={"pk": group.pk})
        response = api_client.patch(url, data=data)
        assert response.status_code == 200
        group = Group.objects.get(name="New group")
        assert group.description == "Group description"

    @pytest.mark.unit
    def test_partial_update_group_description(self, api_client):
        api_client.force_authenticate(self.admin)
        group = Group.objects.create(name="group", description="test description")
        data = {
            "description": "Group description"
        }
        url = reverse('rules-groups-detail', kwargs={"pk": group.pk})
        response = api_client.patch(url, data=data)
        assert response.status_code == 200
        group = Group.objects.get(name="group")
        assert group.description == "Group description"

    @pytest.mark.unit
    def test_partial_update_group_name(self, api_client):
        api_client.force_authenticate(self.admin)
        group = Group.objects.create(name="test", description="test description")
        data = {
            "name": "New group",
        }
        url = reverse('rules-groups-detail', kwargs={"pk": group.pk})
        response = api_client.patch(url, data=data)
        assert response.status_code == 200
        group = Group.objects.get(name="New group")
        assert group.description == "test description"

    @pytest.mark.unit
    def test_add_rule_to_group(self, api_client):
        api_client.force_authenticate(self.admin)
        group = Group.objects.create(name="test", description="test description")

        url = reverse('rules-groups-detail', kwargs={"pk": group.pk})
        response = api_client.get(url)
        assert response.json()['rules'] == []

        data = {"rules": [self.rule1.pk]}
        response = api_client.patch(url, data=data)
        assert response.status_code == 200
        assert len(response.json()['rules']) == 1

    @pytest.mark.unit
    def test_edit_group_in_rule(self, api_client):
        api_client.force_authenticate(self.admin)
        group = Group.objects.create(name="test", description="test description")

        url = reverse('rules-detail', kwargs={"pk": self.rule1.pk})
        response = api_client.get(url)
        assert response.json()['group'] == None

        data = {"group": [group.pk]}
        response = api_client.patch(url, data=data)
        assert response.status_code == 200
        assert response.json()['group'] == {'id': group.pk, 'name': 'test'}
