import aframexr
import unittest

from aframexr.utils.validators import ERROR_MESSAGES


class TestAframexrError(unittest.TestCase):
    """General ERROR tests."""
    def test_from_dict_error_validate_type(self):
        """Verify that the error is raised when using from_dict with no dictionary."""
        invalid_specs = 'not_a_dict'
        with self.assertRaises(TypeError) as error:
            # noinspection PyTypeChecker
            aframexr.Chart.from_dict(specs=invalid_specs)
        self.assertEqual(
            str(error.exception),
            ERROR_MESSAGES['TYPE'].format(
                param_name='specs', expected_type=dict.__name__, current_type=type(invalid_specs).__name__
            )
        )

    def test_data_has_values_and_url(self):
        """Verify that the error is raised when having fields "values" and "url" in data."""
        bad_data_specifications = {'data': {'url': '', 'values': ''}, 'mark': 'bar', 'encoding': {}}
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict(bad_data_specifications).to_html()
        self.assertEqual(str(error.exception), ERROR_MESSAGES['DATA_WITH_VALUES_AND_URL_IN_SPECS'])

    def test_data_has_not_field_data_url(self):
        """Verify that the error is raised when data has no field "data" or "url"."""
        bad_data_specifications = {'data': {}, 'mark': 'bar', 'encoding': {}}  # The same occurs with other marks
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict(bad_data_specifications).to_html()
        self.assertEqual(str(error.exception), ERROR_MESSAGES['DATA_WITH_NOT_VALUES_NEITHER_URL_IN_SPECS'])

    def test_data_of_invalid_type(self):
        """Verify that the error is raised when data has invalid type."""
        err_data = 'invalid data type'
        with self.assertRaises(TypeError) as error:
            aframexr.Chart(err_data).to_html()
        self.assertEqual(
            str(error.exception),
            ERROR_MESSAGES['TYPE'].format(
                param_name='data', expected_type='Data or UrlData or DataFrame', current_type=type(err_data).__name__
            )
        )

    def test_mark_and_element_in_specs(self):
        """Verify that the error is raised when both "mark" and "element" are in chart specifications."""
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict({'mark': '', 'element': ''}).to_html()
        self.assertEqual(str(error.exception), ERROR_MESSAGES['MARK_AND_ELEMENT_IN_SPECS'])

    def test_NULL_series(self):
        """Verify that the error is raised when having NULL series."""
        data = [{"id": 1, "value": None}, {"id": 2, "value": None}, {"id": 3, "value": None}]
        with self.assertRaises(ValueError) as error:
            aframexr.Chart(aframexr.Data(data)).mark_bar().encode(x='id', y='value').to_html()
        self.assertEqual(str(error.exception), 'Unknown dtype: Null.')

    def test_incorrect_equation_syntax(self):
        """Verify that the error is raised when equation has incorrect syntaxis."""
        invalid_equation = 'invalid_equation'
        with self.assertRaises(SyntaxError) as error:
            aframexr.Chart().transform_filter(invalid_equation)
        self.assertEqual(str(error.exception), 'Incorrect syntax, must be datum.{field} {operator} {value}')

    def test_add_error_not_isinstance_TopLevelMixin(self):
        """Verify that the error is raised when adding one chart to another thing."""
        one = aframexr.Chart(aframexr.UrlData(''))
        other = 2
        with self.assertRaises(TypeError) as error:
            one + other
        self.assertEqual(str(error.exception), f"Cannot add {type(other).__name__} to {type(one).__name__}.")

    def test_abstract_class_ElementCreator(self):
        """Verify that the error is raised when creating an ElementCreator object."""
        with self.assertRaises(TypeError) as error:
            aframexr.ElementCreator({})
        self.assertEqual(str(error.exception), 'ElementCreator is abstract')

    def test_moving_concat_chart_error(self):
        """Verify that the error is raised when moving one concatenated chart to another thing."""
        with self.assertRaises(ValueError) as error:
            (aframexr.Box() + aframexr.Box()).movable()
        self.assertEqual(str(error.exception), 'Concatenated charts cannot be movable.')

    def test_invalid_chart_specifications_not_data_having_mark(self):
        """Verify that the error is raised when chart specifications do not have field "data" having "mark"."""
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict({'mark': ''}).show()  # Using show() to cover this method
        self.assertEqual(str(error.exception), ERROR_MESSAGES['DATA_NOT_IN_SPECS'])

    def test_invalid_chart_specifications_not_mark_element(self):
        """Verify that the error is raised when chart specifications do not have field "mark" or "element"."""
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict({'neither_mark_or_element': ''}).to_html()
        self.assertEqual(str(error.exception), ERROR_MESSAGES['MARK_AND_ELEMENT_NOT_IN_SPECS'])

    def test_invalid_chart_type(self):
        """Verify that the error is raised when the chart type is invalid."""
        bad_chart_type = 'bad_mark'
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict({'data': {'url': ''}, 'mark': bad_chart_type, 'encoding': ''}).to_html()
        self.assertEqual(str(error.exception), ERROR_MESSAGES['MARK_TYPE'].format(mark_type=bad_chart_type))

    def test_invalid_element_type(self):
        """Verify that the error is raised when the element type is invalid."""
        bad_element_type = 'bad_element_type'
        with self.assertRaises(ValueError) as error:
            aframexr.Chart.from_dict({'element': bad_element_type}).to_html()
        self.assertEqual(str(error.exception), ERROR_MESSAGES['ELEMENT_TYPE'].format(element=bad_element_type))

    def test_save_invalid_type_format(self):
        """Verify that the error is raised when the save type is invalid."""
        bad_file_format = 'good_file.bad_format'
        with self.assertRaises(ValueError) as error:
            aframexr.Chart().save(bad_file_format)
        self.assertEqual(str(error.exception), 'Invalid file format. Must be "json" or "html"')
