import pytest

import utils.config_parse_helper as helper
from utils.errors import ConfigError, ValidationError


class TestSpecialSchemaProcessingEnum():

    def test_all_enums(self):
        cs = helper.SpecialSchemaProcessing.commma_separated
        assert cs.name == "commma_separated"
        assert cs.value == 1

        ss = helper.SpecialSchemaProcessing.space_separated
        assert ss.name == "space_separated"
        assert ss.value == 2

        assert len(list(helper.SpecialSchemaProcessing)) == 2


class TestConfigSchemaLine():

    def test_initialising_simple_schema_lines(self):
        csl = helper.ConfigSchemaLine('key1', True, bool)
        assert csl.key == 'key1'
        assert csl.required is True
        assert csl.expected_type is bool
        assert csl.special_processing is None

    def test_initialising_list_in_schema(self):
        csl = helper.ConfigSchemaLine('key1', True, list, helper.SpecialSchemaProcessing.commma_separated)
        assert csl.key == 'key1'
        assert csl.required is True
        assert csl.expected_type is list
        assert csl.special_processing is helper.SpecialSchemaProcessing.commma_separated

    def test_init_list_without_special_processing_fails(self):
        not_a_valid_enum = 3
        with pytest.raises(ValidationError):
            helper.ConfigSchemaLine('key1', True, list, not_a_valid_enum)


class TestConfigSchemaSection():

    def test_initalising_schema(self):
        csl1 = helper.ConfigSchemaLine(key='is_cool', required=False, expected_type=bool)
        csl2 = helper.ConfigSchemaLine('fav_word', True, str)
        csl3 = helper.ConfigSchemaLine('some_float', False, float)

        schema = helper.ConfigSchemaSection( 'section_name', [csl1, csl2, csl3] )
        assert schema.options == { 'is_cool' : csl1
                                 , 'fav_word' : csl2
                                 , 'some_float' : csl3
                                 }

    def test_empty_schema_section_ok(self):
        helper.ConfigSchemaSection("ok_with_none", None)
        helper.ConfigSchemaSection("ok_with_empty", [])

    def test_schema_section_fails_if_options_are_not_a_list(self):
        with pytest.raises(ValidationError):
            helper.ConfigSchemaSection("fails_if_not_a_list", True)

        csl = helper.ConfigSchemaLine('key1', True, bool)
        with pytest.raises(ValidationError):
            helper.ConfigSchemaSection("even_fails_on_a_single_csl", csl)

    def test_adding_to_schema(self):
        config = helper.ConfigSchemaSection("adding", [])
        assert config.options == {}

        csl1 = helper.ConfigSchemaLine('key1', True, bool)
        csl2 = helper.ConfigSchemaLine('key2', False, int)
        csl3 = helper.ConfigSchemaLine('key3', True, str)
        config.add_line(csl1)
        assert config.options == { 'key1': csl1 }

        config.add_line(csl2)
        assert config.options == { 'key1': csl1
                                 , 'key2': csl2
                                 }

        config.add_line(csl3)
        assert config.options == { 'key1': csl1
                                 , 'key2': csl2
                                 , 'key3': csl3
                                 }

    def test_adding_a_duplicate_key_to_schema_replaces_previous(self):
        config = helper.ConfigSchemaSection("adding", [])
        assert config.options == {}

        csl_ori = helper.ConfigSchemaLine('key', True, bool)
        csl_alt = helper.ConfigSchemaLine('key', False, int)

        config.add_line(csl_ori)
        assert config.options == { 'key': csl_ori }

        config.add_line(csl_alt)
        assert config.options == { 'key': csl_alt }


class TestStringSplitting():

    def test_split_comma_separated_list(self):
        before_parse = "/hello/sir/, /glorius/filename,/some/actual/file.file"
        expected = [ "/hello/sir/"
                   , "/glorius/filename"
                   , "/some/actual/file.file"
                   ]
        result = helper._split_string_to_list(before_parse, ',')
        assert result == expected

    def test_removal_of_newlines_too(self):
        before_parse = "\n/hello/sir/, /glorius/filename\n,\n/some/actual/file.file"
        expected = [ "/hello/sir/"
                   , "/glorius/filename"
                   , "/some/actual/file.file"
                   ]
        result = helper._split_string_to_list(before_parse, ',')
        assert result == expected
# def _split_string_to_list(items: str, separator=',') -> [str]:

    def test_space_separated_split(self):
        before_parse = "--verbose --progress -a -f -z"
        expected = [ "--verbose"
                   , "--progress"
                   , "-a"
                   , "-f"
                   , "-z"
                   ]
        result = helper._split_string_to_list(before_parse, ' ')
        assert result == expected


class TestParsingConfig():

    def make_simple_config(self, section_name='DEFAULT') -> helper.ConfigParser:
        config = helper.ConfigParser()
        config[section_name] = \
        { 'inty'        : '3'
        , 'floaty'      : '3.141'
        , 'mr_bool'     : 'True'
        , 'stringer'    : "bell"
        , 'listo_comma' : "i,am,comma,separated"
        , 'listo_space' : "yo it's time for space!"
        }
        return config

    def test_parsing_fails_with_missing_required_key(self):
        config = helper.ConfigParser()
        config['DEFAULT'] = { 'not_the_required_key'   : False }

        csl1 = helper.ConfigSchemaLine('Required_key', True, bool)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )

        with pytest.raises(ConfigError):
            helper._parse_config_section(config, schema)

    def test_schema_section_with_only_optional_values_is_ok(self):
        csl1 = helper.ConfigSchemaLine('not_required', False, bool)
        csl2 = helper.ConfigSchemaLine('not_required_either', False, float)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1, csl2] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == {}

    def test_parsing_schema_section_with_ints(self):
        csl1 = helper.ConfigSchemaLine('inty', False, int)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == { 'inty' : 3}

    def test_parsing_schema_section_with_floats(self):
        csl1 = helper.ConfigSchemaLine('floaty', False, float)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == { 'floaty' : 3.141 }

    def test_parsing_schema_section_with_bools(self):
        csl1 = helper.ConfigSchemaLine('mr_bool', True, bool)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == { 'mr_bool' : True }

    def test_parsing_schema_section_with_strs(self):
        csl1 = helper.ConfigSchemaLine('stringer', True, str)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == { 'stringer' : 'bell' }

    def test_parsing_schema_section_with_lists_comma_separated(self):
        csl1 = helper.ConfigSchemaLine('listo_comma', False, list,
            helper.SpecialSchemaProcessing.commma_separated)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == { 'listo_comma' : [ "i"
                                           , "am"
                                           , "comma"
                                           , "separated"
                                           ]
                         }

    def test_parsing_schema_section_with_lists_space_separated(self):
        csl1 = helper.ConfigSchemaLine('listo_space', False, list,
            helper.SpecialSchemaProcessing.space_separated)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        result = helper._parse_config_section(config, schema)
        assert result == { 'listo_space' : [ "yo"
                                           , "it's"
                                           , "time"
                                           , "for"
                                           , "space!"
                                           ]
                         }

    def test_parsing_as_different_types_always_ok_for_str_and_list(self):
        config = self.make_simple_config()

        csl_float = helper.ConfigSchemaLine('floaty', True, float)
        csl_int   = helper.ConfigSchemaLine('floaty', True, int)
        csl_bool  = helper.ConfigSchemaLine('floaty', True, bool)
        csl_str   = helper.ConfigSchemaLine('floaty', True, str)
        csl_list  = helper.ConfigSchemaLine('floaty', True, list,
            helper.SpecialSchemaProcessing.commma_separated)

        schema = helper.ConfigSchemaSection('DEFAULT', [csl_float])
        result = helper._parse_config_section(config, schema)
        assert result == { 'floaty' : 3.141 }
        assert type(result['floaty']) is float

        schema = helper.ConfigSchemaSection('DEFAULT', [csl_int])
        with pytest.raises(ValueError):
            helper._parse_config_section(config, schema)

        schema = helper.ConfigSchemaSection('DEFAULT', [csl_bool])
        with pytest.raises(ValueError):
            helper._parse_config_section(config, schema)

        schema = helper.ConfigSchemaSection('DEFAULT', [csl_str])
        result = helper._parse_config_section(config, schema)
        assert result == { 'floaty' : '3.141' }
        assert type(result['floaty']) is str

        schema = helper.ConfigSchemaSection('DEFAULT', [csl_list])
        result = helper._parse_config_section(config, schema)
        assert result == { 'floaty' : ['3.141'] }
        assert type(result['floaty']) is list

    def test_parsing_schema_line_fails_with_unknown_type(self):
        csl1 = helper.ConfigSchemaLine('floaty', True, dict)
        schema = helper.ConfigSchemaSection('DEFAULT', [csl1] )
        config = self.make_simple_config()
        with pytest.raises(ConfigError):
            helper._parse_config_section(config, schema)

    def test_all_special_processing_options_are_handled(self):
        config = self.make_simple_config()
        config['DEFAULT'] = {'enum_checker' : "comma, and spaces"}

        for e in helper.SpecialSchemaProcessing:
            csl = helper.ConfigSchemaLine('enum_checker', False, list, e)
            schema = helper.ConfigSchemaSection('DEFAULT', [csl])

            result = helper._parse_config_section(config, schema)
            assert type(result['enum_checker']) is list

    def test_parsing_all(self):
        config = self.make_simple_config('FULL_TEST')

        csl1 = helper.ConfigSchemaLine('inty', False, int)
        csl2 = helper.ConfigSchemaLine('floaty', False, float)
        csl3 = helper.ConfigSchemaLine('mr_bool', True, bool)
        csl4 = helper.ConfigSchemaLine('stringer', True, str)
        csl5 = helper.ConfigSchemaLine('listo_comma', False, list,
            helper.SpecialSchemaProcessing.commma_separated)
        csl6 = helper.ConfigSchemaLine('listo_space', False, list,
            helper.SpecialSchemaProcessing.space_separated)
        schema = helper.ConfigSchemaSection('FULL_TEST', [csl1, csl2, csl3, csl4, csl5, csl6])

        result = helper._parse_config_section(config, schema)
        assert result == \
        { 'inty'        : 3
        , 'floaty'      : 3.141
        , 'mr_bool'     : True
        , 'stringer'    : "bell"
        , 'listo_comma' : ["i", "am", "comma", "separated"]
        , 'listo_space' : ["yo", "it's", "time", "for", "space!"]
        }
