"""Test Field Functionality"""

# Protean
import pytest

from protean.core.exceptions import ValidationError
from protean.core.field.basic import Field


class DummyStringField(Field):
    """This is a dummy Field class for testing"""

    default_error_messages = {
        'invalid_type': 'Field value must be of str type.',
    }

    def _cast_to_type(self, value: str):
        """ Value must me a string type"""
        if type(value) != str:
            self.fail('invalid_type')
        return value


class MinLengthValidator:
    def __init__(self, min_length):
        self.min_length = min_length
        self.error = f'Ensure this value has at least ' \
                     f'{self.min_length} character.'

    def __call__(self, value):
        if len(value) < self.min_length:
            raise ValidationError(self.error)


class TestField:

    def test_init(self):
        """Test successful String Field initialization"""

        name = DummyStringField()
        assert name is not None

    def test_required(self):
        """Test errors if required field has no value"""

        with pytest.raises(ValidationError):
            name = DummyStringField(required=True)
            name._load(None)

    def test_defaults(self):
        """ Test default value is set when no value is supplied"""
        # Test with default value as constant
        name = DummyStringField(default='dummy')
        assert name._load('') == 'dummy'

        # Test with default value as callable
        name = DummyStringField(default=lambda: 'dummy')
        assert name._load('') == 'dummy'

    def test_type_validation(self):
        """ Test type checking validation for the Field"""
        with pytest.raises(ValidationError):
            name = DummyStringField()
            name._load(1)

    def test_validators(self):
        """ Test custom validators defined for the field"""

        with pytest.raises(ValidationError):
            name = DummyStringField(
                validators=[MinLengthValidator(min_length=5)])
            name._load('Dum')

    def test_error_message(self):
        """ Test that proper error message is generated"""

        # Test the basic error message
        try:
            name = DummyStringField(required=True)
            name._load(None)
        except ValidationError as err:
            assert err.messages == {
                'unlinked': [name.error_messages['required']]}

        # Test overriding of error message
        try:
            name = DummyStringField()
            name._load(1)
        except ValidationError as err:
            assert err.messages == {
                'unlinked': ['Field value must be of str type.']}

        # Test multiple error messages
        try:
            name = DummyStringField(
                validators=[MinLengthValidator(min_length=5),
                            MinLengthValidator(min_length=5)])
            name._load('Dum')
        except ValidationError as err:
            assert err.messages == {
                'unlinked': ['Ensure this value has at least 5 character.',
                             'Ensure this value has at least 5 character.']}

    def test_default_validators(self):
        """ Test that default validators for a Field are called"""
        def medium_string_validator(value):
            """Function checks the max length of a field"""
            if len(value) > 15:
                raise ValidationError(
                    'Value cannot be more than 15 characters long.')
        DummyStringField.default_validators = [medium_string_validator]
        with pytest.raises(ValidationError):
            name = DummyStringField()
            name._load('Dummy Dummy Dummy')
