# Copyright (c) 2016 AT&T Corp
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import collections
import unittest
from unittest import mock
from yaql.language import contexts as yaql_contexts

from django import forms as django_forms

from muranodashboard.dynamic_ui import fields
from muranodashboard.dynamic_ui import forms
from muranodashboard.dynamic_ui import yaql_expression


class TestAnyFieldDict(unittest.TestCase):

    def test_missing(self):
        any_field_dict = forms.AnyFieldDict()
        result = any_field_dict.__missing__(['foo', 'bar'])
        self.assertEqual('DynamicSelect', result.__name__)


class TestDynamicUiForm(unittest.TestCase):

    def test_collect_fields_process_widget(self):
        test_spec = {
            'type': 'text',
            'name': 'foo_spec',
            'widget_media': {
                'js': 'foo.js',
                'css': 'foo.css'
            },
            'widget_attrs': {'foo': 'bar'}
        }

        result = forms._collect_fields([test_spec], 'foo_form', None)

        self.assertIsInstance(result, list)
        self.assertEqual(1, len(result))
        self.assertEqual('foo_spec', result[0][0])
        self.assertIsInstance(result[0][1], fields.CharField)

        field = result[0][1]

        self.assertTrue(hasattr(field, 'widget'))
        self.assertTrue(hasattr(field.widget, 'Media'))
        self.assertEqual('foo.js', field.widget.Media.js)
        self.assertEqual('foo.css', field.widget.Media.css)
        self.assertIn('foo', field.widget.__dict__['attrs'])
        self.assertEqual('bar', field.widget.__dict__['attrs']['foo'])

    def test_collect_fields_parse_spec_with_yaql_expression(self):
        mock_yaql = mock.Mock(spec=yaql_expression.YaqlExpression)
        test_spec = {
            'type': 'choice',
            'name': 'foo_spec',
            'validators': [{'expr': mock_yaql}]
        }

        result = forms._collect_fields([test_spec], 'foo_form', None)

        self.assertIsInstance(result, list)
        self.assertEqual(1, len(result))
        self.assertEqual('foo_spec', result[0][0])
        self.assertIsInstance(result[0][1], fields.ChoiceField)

        field = result[0][1]

        self.assertTrue(hasattr(field, 'validators'))
        self.assertIn('expr', field.validators[0])
        self.assertIsInstance(field.validators[0]['expr'], fields.RawProperty)
        self.assertEqual(mock_yaql, field.validators[0]['expr'].spec)


class TestDynamicFormMetaclass(unittest.TestCase):

    def test_new(self):
        test_dict = {
            'name': 'foo_form',
            'field_specs': [{'type': 'text', 'name': 'foo_spec'}],
            'service': 'foo_service',
        }

        form = forms.DynamicFormMetaclass('foo_form', (), test_dict)
        self.assertEqual('foo_form', form.__name__)
        self.assertEqual('foo_service', form.service)
        self.assertIsInstance(form.declared_fields, dict)
        self.assertIn('foo_spec', form.declared_fields)
        self.assertIsInstance(form.declared_fields['foo_spec'],
                              fields.CharField)


class TestUpdatableFieldsForm(unittest.TestCase):

    def setUp(self):
        super(TestUpdatableFieldsForm, self).setUp()

        self.form = forms.UpdatableFieldsForm(None)
        self.assertEqual('required', self.form.required_css_class)

    def test_update_fields(self):
        mock_password_field = mock.Mock(spec=fields.PasswordField)
        mock_password_field.confirm_input = True
        mock_password_field.has_clone = False
        mock_password_field.original = True
        mock_password_field.required = True
        mock_password_field.update = mock.Mock()
        mock_password_field.initial = 'foo_initial'
        mock_password_field.get_clone_name.return_value = 'bar_password_field'
        mock_clone_password_field = mock.Mock(
            spec=fields.PasswordField, required=True)
        mock_password_field.clone_field.return_value = \
            mock_clone_password_field

        self.form.fields = collections.OrderedDict({
            'foo_password_field': mock_password_field})
        self.form.update_fields()

        self.assertEqual(2, len(self.form.fields))
        self.assertIn('foo_password_field', self.form.fields)
        self.assertIn('bar_password_field', self.form.fields)
        self.assertEqual(mock_password_field,
                         self.form.fields['foo_password_field'])
        self.assertEqual(mock_clone_password_field,
                         self.form.fields['bar_password_field'])
        mock_password_field.get_clone_name.assert_called_once_with(
            'foo_password_field')
        self.assertTrue(mock_password_field.get_clone_name.called)
        self.assertTrue(mock_password_field.update.called)


class TestServiceConfigurationForm(unittest.TestCase):

    def setUp(self):
        super(TestServiceConfigurationForm, self).setUp()

        mock_service = mock.Mock()
        mock_service.update_cleaned_data.return_value = \
            {'foo': 'bar', 'baz': 'qux'}

        self.form = forms.ServiceConfigurationForm(
            initial={'app_id': 'foo_app'})
        self.form._errors = []
        self.form.validators = []
        self.form.cleaned_data = {'foo': 'bar', 'baz': 'qux'}
        self.form.service = mock_service

        self.assertEqual('foo_app_%s', self.form.auto_id)
        self.assertIsInstance(self.form.context, yaql_contexts.Context)

    @mock.patch.object(forms, 'LOG', autospec=True)
    def test_clean(self, mock_log):
        password_field = mock.Mock(spec=fields.PasswordField)
        password_field.enabled = True
        password_field.confirm_input = True

        foo_field = mock.Mock()
        foo_field.enabled = False
        foo_field.postclean.return_value = 'post_foo'

        self.form.fields = {'foo': foo_field, 'password': password_field}
        result = self.form.clean()
        expected_cleaned_data = {'foo': 'post_foo', 'baz': 'qux'}

        for key, val in expected_cleaned_data.items():
            self.assertEqual(val, result[key])

        # NOTE(felipemonteiro): mock.ANY is being used in the assertions
        # below, rather than `{'foo': 'bar', 'baz': 'qux'}` because
        # `cleaned_data[name] = value` in clean() appears to also change the
        # dict that was passed in to mock objects in previous lines of code.
        foo_field.postclean.assert_called_once_with(self.form, 'foo', mock.ANY)
        password_field.compare.assert_called_once_with('password', mock.ANY)
        mock_log.debug.assert_called_once_with(
            "Update 'foo' data in postclean method")
        self.form.service.update_cleaned_data.assert_called_with(
            mock.ANY, form=self.form)

    def test_clean_except_validation_error(self):
        mock_expr = mock.Mock()
        mock_expr.evaluate.return_value = False
        test_validator = {'expr': mock_expr, 'message': 'Foo Error'}

        self.form.validators = [test_validator]
        with self.assertRaises(django_forms.ValidationError) as cm:
            self.form.clean()
        e = cm.exception
        self.assertIn('Foo Error', e.messages)

        self.form.service.update_cleaned_data.assert_called_once_with(
            {'foo': 'bar', 'baz': 'qux'}, form=self.form)
        mock_expr.evaluate.assert_called_once_with(
            data={'foo': 'bar', 'baz': 'qux'}, context=self.form.context)

    def test_clean_with_errors(self):
        self.form._errors = ['foo_error']
        self.assertEqual({'foo': 'bar', 'baz': 'qux'}, self.form.clean())
