import base64
import json
import logging

from django.shortcuts import get_object_or_404
from rest_framework.exceptions import APIException

from devices.models.endpoint_device import EndpointModel
from devices.serializers.endpoint_serializers import EndpointConfigSerializer
from devices.services.endpoint.endpoint_get_status import EndpointStatusService
from devices.services.endpoint.endpoint_redis import RedisInterface
from devices.services.vector import VectorService

_log = logging.getLogger(__name__)


class EndpointException(APIException):
    status_code = 400
    default_detail = 'Cannot create endpoint.'


class EndpointManagementService:
    """Service to management endpoint."""

    def __init__(self, instance: EndpointModel) -> None:
        self.instance = instance
        self._log_service = VectorService(instance)

    def create(self):
        _log.debug(f'Create endpoint logger config')
        try:
            self._log_service.update_config()
        except Exception as err:
            _log.error(f'Cannot create log file: {err}')
            raise EndpointException(str(err))

    def update(self):
        _log.debug(f'Update endpoint logger config')
        try:
            self._log_service.update_config()
        except Exception as err:
            _log.error(f'Cannot update logger config')
            raise EndpointException(str(err))

    def destroy(self):
        _log.debug(f'Destroy endpoint logger config')
        try:
            self._log_service.delete_config()
        except Exception as err:
            _log.error(f'Cannot delete logger config')
            raise EndpointException(str(err))


class EndpointKepAliveService:

    def __init__(self, pk: int, data) -> None:
        self.endpoint = EndpointModel.objects.get(pk=pk)
        self.data = json.loads(data.decode('utf-8'))

    def get_response(self) -> dict:
        redis_interface = RedisInterface()
        redis_interface.set_keepalive(self.endpoint.pk)

        status = self.data['status']

        if status == 'error':
            self.endpoint.config_errors = self.data['errors']
            self.endpoint.incorrect_settings = True
            self.endpoint.save()

        if self.endpoint.settings_changed and self.data['status'] == 'ok':
            self.endpoint.settings_changed = False
            self.endpoint.incorrect_settings = False
            self.endpoint.config_errors = None
            self.endpoint.save()
            return {'status': 'ok', 'command': 'setting change'}

        if self.endpoint.request_config:
            return {'status': 'ok', 'command': 'upload'}

        return {'status': 'ok'}


class EndpointDownloadConfigService:
    def __init__(self, pk: int) -> None:
        self.endpoint = get_object_or_404(EndpointModel, pk=pk)

    def _dict_to_json(self, config: dict) -> bytes:
        json_str = json.dumps(config, ensure_ascii=False,
                              indent=4, sort_keys=True).encode("utf-8")
        return json_str

    def setup_endpoint(self) -> None:
        if self.endpoint.antivirus_start_scan:
            self.endpoint.antivirus_start_scan = False
            self.endpoint.save()

    def download(self) -> dict:
        config = self.save_endpoint_settings_to_dict()
        # Serialize data to string
        json_str = self._dict_to_json(config)
        base64_bytes = base64.b64encode(json_str)
        base64_message = base64_bytes.decode("utf-8")

        response = {
            'status': 'ok',
            'config': base64_message
        }
        return response

    def save_endpoint_settings_to_dict(self) -> dict:
        """Convert endpoint model to dict"""
        integrity_control_scan_paths = self.endpoint.scan_paths if self.endpoint.scan_paths else []
        white_list_paths = self.endpoint.white_list_paths if self.endpoint.white_list_paths else []
        antivirus_paths = self.endpoint.antivirus_paths if self.endpoint.antivirus_paths else []

        config = {
            "device_control": {
                "cd": {
                    "deny_read": self.endpoint.prohibit_cd_access,
                    "deny_write": self.endpoint.prohibit_cd_access
                },
                "enabled": self.endpoint.device_control_enabled,
            },
            "integrity_control": {
                "control_path": integrity_control_scan_paths,
                "enabled": self.endpoint.integrity_control_enabled,
                "timeout": f'{self.endpoint.integrity_control_timeout}s',

            },
            "white_list": {
                "enabled": self.endpoint.whitelist_enabled,
                "local_admin": self.endpoint.whitelist_admin,
                "path": white_list_paths,
            },
            "usb_control": {
                "enabled": self.endpoint.usb_control_enabled,
            },
            "antivirus": {
                "start_scan": self.endpoint.antivirus_start_scan,
                "scan_path": antivirus_paths,
                "enabled": self.endpoint.antivirus_enabled,
                "remove_infected_files": self.endpoint.antivirus_remove_infected_files,
            },
        }

        rotation = {
            'type': self.endpoint.event_rotation_type,  # Int
            'size': self.endpoint.event_rotation_size,  # Int
            'period': self.endpoint.event_rotation_period,  # Int
            'time': self.endpoint.event_rotation_time,  # String in format: 01:00:00
        }

        config['rotation'] = rotation
        return config

    def download_as_file(self) -> tuple:
        config = self.save_endpoint_settings_to_dict()
        filename = f'endpoint_config_{self.endpoint.pk}.json'
        return self._dict_to_json(config), filename


class EndpointUploadConfigService:
    def __init__(self, pk: int, data) -> None:
        self.endpoint = EndpointModel.objects.get(pk=pk)
        self.data = json.loads(data.decode('utf-8'))
        self.prepare_data()

    def prepare_data(self):
        """Data preparation, mapping endpoint fields to DB names."""
        mapping_fields = {
            # Endpoint/MC
            'dc_enabled': 'device_control_enabled',

            'ic_enabled': 'integrity_control_enabled',
            'scan_folders': 'scan_paths',
            'ic_timeout': 'integrity_control_timeout',

            'wl_enable': 'whitelist_enabled',
            'wl_admin': 'whitelist_admin',
            'white_list': 'white_list_paths',

            'clamav_enabled': 'antivirus_enabled',
            'clamav_paths': 'antivirus_paths',
            'clamav_remove_infected_files': 'antivirus_remove_infected_files',
            'clamav_start_scan': 'antivirus_start_scan',
        }

        self.data['ip'] = self.endpoint.ip
        for alias, field in mapping_fields.items():
            try:
                self.data[field] = self.data[alias]
            except KeyError:
                continue

    def upload(self):
        serializer = EndpointConfigSerializer(self.endpoint, data=self.data)
        self.endpoint.request_config = False
        if not serializer.is_valid():
            _log.error(serializer.errors)
            self.endpoint.is_requested_config_correct = False
            self.endpoint.save()
            return {'status': 'error', 'error_message': serializer.errors}

        serializer.update(instance=self.endpoint, validated_data=serializer.validated_data)
        self.endpoint.is_requested_config_correct = True
        self.endpoint.incorrect_settings = False
        self.endpoint.config_errors = None
        self.endpoint.save()
        _log.info(f'Endpoint [{self.endpoint.pk}] updated form endpoint instance')
        return {'status': 'ok'}


class EndpointUpdateService:
    """Service for update config from endpoint."""

    def __init__(self, pk: int) -> None:
        self.endpoint = get_object_or_404(EndpointModel, pk=pk)
        self.status_service = EndpointStatusService(self.endpoint)

    def _update(self) -> None:
        self.endpoint.request_config = True
        self.endpoint.save()

    def update(self) -> dict:
        status = self.status_service.get_status()['status']
        if status == 'offline':
            raise EndpointException('Endpoint is offline')
        self._update()
        return {'status': 'ok'}
