"""Test that what we feed to Cerberus actually works.

This'll help us upgrade to new versions of Cerberus.
"""

import unittest

from pillar.api.node_types.utils import markdown_fields
from pillar.tests import AbstractPillarTest

from bson import ObjectId


class CerberusCanaryTest(unittest.TestCase):

    def _canary_test(self, validator):
        groups_schema = {'name': {'type': 'string', 'required': True}}

        # On error, validate_schema() raises ValidationError
        if hasattr(validator, 'validate_schema'):
            # It was removed in Cerberus 1.0 (or thereabouts)
            validator.validate_schema(groups_schema)

        # On error, validate() returns False
        self.assertTrue(validator.validate({'name': 'je moeder'}, groups_schema))
        self.assertFalse(validator.validate({'je moeder': 'op je hoofd'}, groups_schema))

    def test_canary(self):
        import cerberus

        validator = cerberus.Validator()
        self._canary_test(validator)

    def test_our_validator_simple(self):
        from pillar.api import custom_field_validation

        validator = custom_field_validation.ValidateCustomFields()
        self._canary_test(validator)


class AbstractValidationTest(AbstractPillarTest):
    def setUp(self):
        super().setUp()

        from pillar.api import custom_field_validation

        self.validator = custom_field_validation.ValidateCustomFields()
        self.user_id = ObjectId(8 * 'abc')
        self.ensure_user_exists(self.user_id, 'Tést Üsâh')

    def assertValid(self, document, schema):
        with self.app.app_context():
            is_valid = self.validator.validate(document, schema)
        self.assertTrue(is_valid, f'errors: {self.validator.errors}')

    def assertInvalid(self, document, schema):
        with self.app.app_context():
            is_valid = self.validator.validate(document, schema)
        self.assertFalse(is_valid)


class ProjectValidationTest(AbstractValidationTest):

    def test_empty(self):
        from pillar.api.eve_settings import projects_schema
        self.assertInvalid({}, projects_schema)

    def test_simple_project(self):
        from pillar.api.eve_settings import projects_schema

        project = {
            'name': 'Té Ærhüs',
            'user': self.user_id,
            'category': 'assets',
            'is_private': False,
            'status': 'published',
        }

        self.assertValid(project, projects_schema)

    def test_with_node_types(self):
        from pillar.api.eve_settings import projects_schema
        from pillar.api import node_types

        project = {
            'name': 'Té Ærhüs',
            'user': self.user_id,
            'category': 'assets',
            'is_private': False,
            'status': 'published',
            'node_types': [node_types.node_type_asset,
                           node_types.node_type_comment]
        }

        self.assertValid(project, projects_schema)


class NodeValidationTest(AbstractValidationTest):
    def setUp(self):
        super().setUp()
        self.pid, self.project = self.ensure_project_exists()

    def test_empty(self):
        from pillar.api.eve_settings import nodes_schema
        self.assertInvalid({}, nodes_schema)

    def test_asset(self):
        from pillar.api.eve_settings import nodes_schema

        file_id, _ = self.ensure_file_exists()

        node = {
            'name': '"The Harmless Prototype™"',
            'project': self.pid,
            'node_type': 'asset',
            'properties': {
                'status': 'published',
                'content_type': 'image',
                'file': file_id,
            },
            'user': self.user_id,
            'short_code': 'ABC333',
        }
        self.assertValid(node, nodes_schema)

    def test_asset_invalid_properties(self):
        from pillar.api.eve_settings import nodes_schema

        file_id, _ = self.ensure_file_exists()

        node = {
            'name': '"The Harmless Prototype™"',
            'project': self.pid,
            'node_type': 'asset',
            'properties': {
                'status': 'invalid-status',
                'content_type': 'image',
                'file': file_id,
            },
            'user': self.user_id,
            'short_code': 'ABC333',
        }
        self.assertInvalid(node, nodes_schema)

    def test_comment(self):
        from pillar.api.eve_settings import nodes_schema

        file_id, _ = self.ensure_file_exists()

        node = {
            'name': '"The Harmless Prototype™"',
            'project': self.pid,
            'node_type': 'asset',
            'properties': {
                'status': 'published',
                'content_type': 'image',
                'file': file_id,
            },
            'user': self.user_id,
            'short_code': 'ABC333',
        }
        node_id = self.create_node(node)

        comment = {
            'name': 'comment on some node',
            'project': self.pid,
            'node_type': 'comment',
            'properties': {
                'content': 'this is a comment',
                'status': 'published',
            },
            'parent': node_id,
        }
        self.assertValid(comment, nodes_schema)


class AbstractSchemaValidationTest(AbstractValidationTest):
    schema = {}

    def assertValid(self, document, schema=None):
        return super().assertValid(document, schema or self.schema)

    def assertInvalid(self, document, schema=None):
        return super().assertInvalid(document, schema or self.schema)


class IPRangeValidatorTest(AbstractSchemaValidationTest):
    schema = {'iprange': {'type': 'string', 'required': True, 'check_with': 'iprange'}}

    def test_ipv6(self):
        self.assertValid({'iprange': '2a03:b0c0:0:1010::8fe:6ef1'})
        self.assertValid({'iprange': '0:0:0:0:0:ffff:102:304'})
        self.assertValid({'iprange': '2a03:b0c0:0:1010::8fe:6ef1/120'})
        self.assertValid({'iprange': 'ff06::/8'})
        self.assertValid({'iprange': '::/8'})
        self.assertValid({'iprange': '::/1'})
        self.assertValid({'iprange': '::1/128'})
        self.assertValid({'iprange': '::'})
        self.assertInvalid({'iprange': '::/0'})
        self.assertInvalid({'iprange': 'barbled'})

    def test_ipv4(self):
        self.assertValid({'iprange': '1.2.3.4'})
        self.assertValid({'iprange': '1.2.3.4/24'})
        self.assertValid({'iprange': '127.0.0.0/8'})
        self.assertInvalid({'iprange': '127.0.0.0/0'})
        self.assertInvalid({'iprange': 'garbled'})

    def test_descriptive_error_message(self):
        is_valid = self.validator.validate({'iprange': '::/0'}, self.schema)
        self.assertFalse(is_valid)
        self.assertEqual(1, len(self.validator._errors))
        err = self.validator._errors[0]
        self.assertEqual(('iprange', ), err.document_path)
        self.assertEqual(('Zero-length prefix is not allowed',), err.info)


class MarkdownValidatorTest(AbstractSchemaValidationTest):
    schema = {'subdoc': {
        'type': 'list',
        'schema': {
            'type': 'dict',
            'schema': {
                **markdown_fields('content', required=True),
                **markdown_fields('descr', required=True),
                'my_default_value': {'type': 'string', 'default': 'my default value'},
            }
        },
    }}

    def test_in_subdoc(self):
        doc = {'subdoc': [{
            'content': '# Header\n\nSome text',
            'descr': 'je moeder'
        }]}
        self.validator.validate(doc, self.schema, normalize=True)

        expect = {'subdoc': [{
            'content': '# Header\n\nSome text',
            '_content_html': '<h1>Header</h1>\n<p>Some text</p>\n',
            'descr': 'je moeder',
            '_descr_html': '<p>je moeder</p>\n',
            'my_default_value': 'my default value'
        }]}

        self.assertEqual(expect, self.validator.document)
