import os
from unittest import mock

import pytest
import requests
from django.urls import reverse
from rest_framework import status

from devices.models.device import Device, DeviceGroup
from devices.models.firewall import ArmaIndustrialFirewall
from devices.services.firewall import InvalidCredentialException, IncompatibilityVersionException, ConnectionException, \
    InvalidResponseException, FailedUploadException, InvalidFileException

FIREWALL_DATA = {
    "name": "ADD IFTEST",
    "ip": "192.168.56.103",
    "key": "nWM0Pnj4w3DJHbkfIRQ2CbUdqIc0TMUYIHohRCSqWJ5TycVfLo3JlIyurmOXN7MaRMQv/hlUIPbD89Ng",
    "secret": "veREg8dbHC/V4hSCi6LBzuQ0NF5eeS/50d7K7Ahut6X0N/77peVQE5ucIJ/fyKhp0RNlbCHEcen2Rk8U",
    "port": 5000,
    "type": 'firewall'
}

TEST_REQUEST_RESPONSE_LIST = [
    requests.exceptions.ConnectTimeout,
    requests.exceptions.ConnectionError,
    requests.exceptions.Timeout
]

check_connection_exceptions = [InvalidCredentialException, IncompatibilityVersionException, ConnectionException]
upload_file_exceptions = [InvalidFileException, FailedUploadException, InvalidResponseException]

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEST_FILES = os.path.join(BASE_DIR, "tests", "test_files")


def _get_json_mock(value):
    return value


@mock.patch('devices.services.firewall.firewall.FIREWALL_TIMEOUT', 1)
@pytest.mark.django_db
class TestFirewallAPI:

    @pytest.fixture(autouse=True)
    def setup_tests(self, api_client, django_user_model, add_user_with_permissions):
        self.user = add_user_with_permissions(username='test_admin', password='test_admin_pass', is_superuser=True)

    @pytest.mark.unit
    def test_getting_list_of_firewalls(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        url = reverse('firewall-list')
        response = api_client.get(url)
        assert response.json()['count'] == 1
        assert response.json()['results'][0]['id'] == firewall.id

    @pytest.mark.unit
    def test_getting_firewall(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        url = reverse('firewall-detail', args=[firewall.pk])
        response = api_client.get(url)
        assert response.status_code == status.HTTP_200_OK
        assert response.json()['id'] == firewall.id

    @pytest.mark.unit
    def test_getting_firewall_with_invalid_id(self, api_client):
        api_client.force_authenticate(self.user)
        url = reverse('firewall-detail', args=[56789])
        response = api_client.get(url)
        assert response.status_code == status.HTTP_404_NOT_FOUND

    @pytest.mark.unit
    @mock.patch('devices.services.firewall.firewall.FirewallService.check_connection')
    def test_updating_firewall_with_valid_data(self, mock_check, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        url = reverse('firewall-detail', args=[firewall.id])
        new_data = FIREWALL_DATA.copy()
        new_data['port'] = 4545
        new_data['name'] = 'new_firewall'
        response = api_client.patch(url, data=new_data)
        assert response.status_code == status.HTTP_200_OK
        assert response.json()['id'] == firewall.id
        assert response.json()['port'] == new_data['port']
        assert response.json()['port'] == new_data['port']
        assert response.json()['name'] == new_data['name']

    @pytest.mark.unit
    def test_updating_firewall_with_invalid_port(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        url = reverse('firewall-detail', args=[firewall.id])
        new_data = {
            'name': 'new_firewall',
            'port': 99999
        }
        response = api_client.patch(url, data=new_data)
        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert 'port' in response.json()
        assert response.json()['port'] == ['Ensure this value is less than or equal to 65535.']

    @pytest.mark.unit
    def test_updating_firewall_with_connection_error(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        firewall_id = firewall.id
        url = reverse('firewall-detail', args=[firewall_id])
        new_data = FIREWALL_DATA.copy()
        new_data.update({'name': 'new_firewall', 'port': 9999})
        with mock.patch('requests.Session.get', side_effect=ConnectionException):
            response = api_client.patch(url, data=new_data)
        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert response.json()['detail'] == 'There was a problem connecting to the firewall'

        firewall = ArmaIndustrialFirewall.objects.get(pk=firewall_id)
        assert firewall.name == FIREWALL_DATA['name']
        assert firewall.port == FIREWALL_DATA['port']

    @pytest.mark.unit
    @mock.patch('devices.services.firewall.firewall.FirewallService.check_connection')
    def test_updating_firewall_null_group(self, mock_check, api_client):
        api_client.force_authenticate(self.user)
        device_group = DeviceGroup.objects.create(name='IF test group')
        data = FIREWALL_DATA.copy()
        data['group'] = device_group
        firewall = ArmaIndustrialFirewall.objects.create(**data)
        url = reverse('firewall-detail', args=[firewall.id])
        data['group'] = ''
        response = api_client.patch(url, data=data)
        assert response.status_code == status.HTTP_200_OK
        assert response.data['group'] is None

    @pytest.mark.unit
    def test_creating_firewall_without_ip(self, api_client):
        api_client.force_authenticate(self.user)
        url = reverse('firewall-list')
        data = FIREWALL_DATA.copy()
        del data['ip']
        response = api_client.post(url, data=data)
        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert 'ip' in response.json()
        assert response.json()['ip'] == ['This field is required.']

    @pytest.mark.unit
    @pytest.mark.parametrize('req_res', TEST_REQUEST_RESPONSE_LIST)
    def test_create_firewall_with_network_error(self, req_res, api_client):
        api_client.force_authenticate(self.user)
        with mock.patch('requests.Session.get', side_effect=req_res):
            url = reverse('firewall-list')
            response = api_client.post(url, data=FIREWALL_DATA)
            assert response.json()['detail'] == 'There was a problem connecting to the firewall'

    @pytest.mark.unit
    @pytest.mark.parametrize('exc', check_connection_exceptions)
    def test_create_firewall_with_error_check_connection(self, exc, api_client):
        api_client.force_authenticate(self.user)
        with mock.patch('devices.services.firewall.FirewallService.check_connection', side_effect=exc):
            url = reverse('firewall-list')
            data = FIREWALL_DATA.copy()
            data['key'] = 'invalid'
            response = api_client.post(url, data=data)
            assert response.json()['detail'] == exc.default_detail['detail']

    @pytest.mark.unit
    def test_delete_firewall(self, api_client):
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        assert ArmaIndustrialFirewall.objects.filter(id=firewall.id).exists()
        assert Device.objects.filter(id=firewall.id).exists()

        api_client.force_authenticate(self.user)
        url = reverse('firewall-detail', args=[firewall.id])
        response = api_client.delete(url)
        assert response.status_code == status.HTTP_204_NO_CONTENT
        assert not ArmaIndustrialFirewall.objects.filter(id=firewall.id).exists()
        assert not Device.objects.filter(id=firewall.id).exists()

    @pytest.mark.unit
    @pytest.mark.parametrize('exc', upload_file_exceptions)
    def test_upload_firewall_config_error(self, exc, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)

        file_path = os.path.join(TEST_FILES, 'config.xml')
        file = open(file_path, 'r')
        url = reverse('firewall-upload-config', args=[firewall.id])
        data = {'conffile': file}
        with mock.patch('devices.services.firewall.FirewallService.upload_file', side_effect=exc):
            response = api_client.post(url, data)
        assert response.json()['detail'] == exc.default_detail['detail']
        assert response.status_code == status.HTTP_400_BAD_REQUEST

    @pytest.mark.unit
    def test_upload_firewall_config_without_file(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)

        url = reverse('firewall-upload-config', args=[firewall.id])
        response = api_client.post(url)
        assert 'conffile' in response.json()
        assert response.status_code == status.HTTP_400_BAD_REQUEST

    @pytest.mark.unit
    @mock.patch('devices.services.firewall.FirewallService.upload_file', lambda *args: {'status': 'ok'})
    def test_upload_firewall_config_success(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)

        file_path = os.path.join(TEST_FILES, 'config.xml')
        file = open(file_path, 'r')
        url = reverse('firewall-upload-config', args=[firewall.id])
        data = {'conffile': file}

        response = api_client.post(url, data)
        assert response.json()['status'] == 'ok'
        assert response.status_code == status.HTTP_200_OK

    @pytest.mark.unit
    @mock.patch('devices.services.firewall.FirewallService.reboot', lambda *args: {'status': 'ok'})
    def test_reboot_firewall_success(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)

        url = reverse('firewall-reboot', args=[firewall.id])
        response = api_client.post(url)
        assert response.json()['status'] == 'ok'
        assert response.status_code == status.HTTP_200_OK

    # Without IF parallel execution
    @pytest.mark.integration
    def test_get_firewall_status(self, api_client):
        api_client.force_authenticate(self.user)
        firewall = ArmaIndustrialFirewall.objects.create(**FIREWALL_DATA)
        url = reverse('firewall-status', args=[firewall.id])
        response = api_client.get(url)
        assert response.status_code == status.HTTP_200_OK
