from sshpubkeys import (
    SSHKey as SSHKey_,
    InvalidKeyException,
    TooShortKeyException,
    TooLongKeyException,
    InvalidTypeException,
    MalformedDataException,
)

from django.conf import settings
from django.forms import ValidationError

from staff.keys.models import SSHKey, BannedSSHKey
from staff.lib.forms.staff_form import StaffForm
from staff.lib.forms.staff_fields import StaffCharField, StaffIntegerField
from staff.person.models import Staff

from staff.person_profile.controllers.ssh_key import ssh_fingerprint, SSHKeyError


class SSHKeyValidator(object):
    # при пополонении алгоритмов достаточно добавлять пары в этот dict
    valid_bits = {
        'ssh-rsa': 2047,
        'ecdsa-sha2-nistp256': 256,
        'ecdsa-sha2-nistp384': 384,
        'ecdsa-sha2-nistp521': 521,
    }

    max_key_length = 4096
    allowed_ssh_keys_duplicates_fingerprints = settings.ALLOWED_SSH_KEYS_DUPLICATES_FINGERPRINTS

    def __call__(self, value, person: Staff):
        try:
            if not value:
                raise InvalidKeyException()

            ssh = SSHKey_(value)

            key_valid_bits = self.valid_bits.get(ssh.key_type.decode('utf-8'))

            if key_valid_bits is None:
                raise InvalidTypeException()
            if ssh.bits < key_valid_bits:
                raise TooShortKeyException()
            if ssh.bits > self.max_key_length:
                raise TooLongKeyException()

        except TooShortKeyException:
            raise ValidationError(
                '{"error_key": "staff-too_short_ssh_key_field"}',
                code='staff_too_short_ssh_key_field',
            )
        except TooLongKeyException:
            raise ValidationError(
                '{"error_key": "staff-too_long_ssh_key_field"}',
                code='staff_too_long_ssh_key_field',
            )
        except InvalidTypeException:
            raise ValidationError(
                '{"error_key": "staff-invalid_type_ssh_key_field"}',
                code='staff_invalid_type_ssh_key_field',
            )
        except MalformedDataException:
            raise ValidationError(
                '{"error_key": "staff-malformed_data_ssh_key_field"}',
                code='staff_malformed_data_ssh_key_field',
            )
        except InvalidKeyException:
            raise ValidationError(
                '{"error_key": "staff-invalid_key_ssh_key_field"}',
                code='staff_invalid_key_ssh_key_field',
            )
        except Exception:
            raise ValidationError(
                '{"error_key": "staff-general_ssh_key_field"}',
                code='staff_general_ssh_key_field',
            )

        fingerprint_sha256 = ssh_fingerprint(value, algo='sha256')

        if BannedSSHKey.objects.filter(fingerprint_sha256=fingerprint_sha256).exists():
            raise ValidationError(
                '{"error_key": "staff-compromised_key_ssh_key_field"}',
                code='staff_compromised_key_ssh_key_field',
            )

        if fingerprint_sha256 not in self.allowed_ssh_keys_duplicates_fingerprints:
            existing_keys = (
                SSHKey.objects
                .exclude(staff=person, intranet_status=0)
                .filter(fingerprint_sha256=fingerprint_sha256)
            )

            if existing_keys.exists():
                raise ValidationError(
                    '{"error_key": "staff-already_used_key_ssh_key_field"}',
                    code='staff_already_used_key_ssh_key_field',
                )


ssh_key_validator = SSHKeyValidator()


class SSHKeyIdValidator(object):

    def __call__(self, value):
        if value and not SSHKey.objects.filter(id=value, intranet_status=1).exists():
            raise ValidationError(
                '{"error_key": "staff-invalid_ssh_key_id"}',
                code='staff_invalid_ssh_key_id')


ssh_key_id_validator = SSHKeyIdValidator()


class SSHKeyForm(StaffForm):
    strip_fields = ['key', 'description']

    id = StaffIntegerField(
        required=False,
        validators=[ssh_key_id_validator],
        front_params={'hidden': True},
    )
    fingerprint = StaffCharField(required=False)
    fingerprint_sha256 = StaffCharField(required=False)
    key = StaffCharField(required=False)
    description = StaffCharField(max_length=255, required=False)

    def _has_id(self):
        return bool(self.cleaned_data.get('id'))

    def clean(self):
        cleaned_data = super(SSHKeyForm, self).clean()
        if self._has_id():
            cleaned_data.pop('key', None)
            cleaned_data.pop('fingerprint', None)
            cleaned_data.pop('fingerprint_sha256', None)
        else:
            key = cleaned_data['key'].strip().replace('\n', '')
            try:
                ssh_key_validator(key, self.data['person'])
            except ValidationError as e:
                self._errors['key'] = [e.message]
                return cleaned_data

            try:
                cleaned_data['fingerprint'] = ssh_fingerprint(key)
                cleaned_data['fingerprint_sha256'] = ssh_fingerprint(key, algo='sha256')
                cleaned_data['key'] = key
            except SSHKeyError:
                self._errors['key'] = ['{"error_key": "staff-general_ssh_key_field"}']
        return cleaned_data
