import subprocess

import pytest

from hatch.config.model import ConfigurationError, RootConfig


def test_default(default_cache_dir, default_data_dir):
    config = RootConfig({})
    config.parse_fields()

    assert config.raw_data == {
        'mode': 'local',
        'project': '',
        'shell': '',
        'dirs': {
            'project': [],
            'env': {},
            'python': 'isolated',
            'data': str(default_data_dir),
            'cache': str(default_cache_dir),
        },
        'projects': {},
        'publish': {'index': {'repo': 'main'}},
        'template': {
            'name': 'Foo Bar',
            'email': 'foo@bar.baz',
            'licenses': {'default': ['MIT'], 'headers': True},
            'plugins': {'default': {'ci': False, 'src-layout': True, 'tests': True}},
        },
        'terminal': {
            'styles': {
                'info': 'bold',
                'success': 'bold cyan',
                'error': 'bold red',
                'warning': 'bold yellow',
                'waiting': 'bold magenta',
                'debug': 'bold',
                'spinner': 'simpleDotsScrolling',
            },
        },
    }


class TestMode:
    def test_default(self):
        config = RootConfig({})

        assert config.mode == config.mode == 'local'
        assert config.raw_data == {'mode': 'local'}

    def test_defined(self):
        config = RootConfig({'mode': 'aware'})

        assert config.mode == 'aware'
        assert config.raw_data == {'mode': 'aware'}

    def test_not_string(self, helpers):
        config = RootConfig({'mode': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                mode
                  must be a string"""
            ),
        ):
            _ = config.mode

    def test_unknown(self, helpers):
        config = RootConfig({'mode': 'foo'})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                mode
                  must be one of: aware, local, project"""
            ),
        ):
            _ = config.mode

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.mode = 9000
        assert config.raw_data == {'mode': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                mode
                  must be a string"""
            ),
        ):
            _ = config.mode


class TestProject:
    def test_default(self):
        config = RootConfig({})

        assert config.project == config.project == ''
        assert config.raw_data == {'project': ''}

    def test_defined(self):
        config = RootConfig({'project': 'foo'})

        assert config.project == 'foo'
        assert config.raw_data == {'project': 'foo'}

    def test_not_string(self, helpers):
        config = RootConfig({'project': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                project
                  must be a string"""
            ),
        ):
            _ = config.project

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.project = 9000
        assert config.raw_data == {'project': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                project
                  must be a string"""
            ),
        ):
            _ = config.project


class TestShell:
    def test_default(self):
        config = RootConfig({})

        assert config.shell.name == config.shell.name == ''
        assert config.shell.path == config.shell.path == ''
        assert config.shell.args == config.shell.args == []
        assert config.raw_data == {'shell': ''}

    def test_invalid_type(self, helpers):
        config = RootConfig({'shell': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell
                  must be a string or table"""
            ),
        ):
            _ = config.shell

    def test_string(self):
        config = RootConfig({'shell': 'foo'})

        assert config.shell.name == 'foo'
        assert config.shell.path == 'foo'
        assert config.shell.args == []
        assert config.raw_data == {'shell': 'foo'}

    def test_table(self):
        config = RootConfig({'shell': {'name': 'foo'}})

        assert config.shell.name == 'foo'
        assert config.shell.path == 'foo'
        assert config.shell.args == []
        assert config.raw_data == {'shell': {'name': 'foo', 'path': 'foo', 'args': []}}

    def test_table_with_path(self):
        config = RootConfig({'shell': {'name': 'foo', 'path': 'bar'}})

        assert config.shell.name == 'foo'
        assert config.shell.path == 'bar'
        assert config.shell.args == []
        assert config.raw_data == {'shell': {'name': 'foo', 'path': 'bar', 'args': []}}

    def test_table_with_path_and_args(self):
        config = RootConfig({'shell': {'name': 'foo', 'path': 'bar', 'args': ['baz']}})

        assert config.shell.name == 'foo'
        assert config.shell.path == 'bar'
        assert config.shell.args == ['baz']
        assert config.raw_data == {'shell': {'name': 'foo', 'path': 'bar', 'args': ['baz']}}

    def test_table_no_name(self, helpers):
        config = RootConfig({'shell': {}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> name
                  required field"""
            ),
        ):
            _ = config.shell.name

    def test_table_name_not_string(self, helpers):
        config = RootConfig({'shell': {'name': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> name
                  must be a string"""
            ),
        ):
            _ = config.shell.name

    def test_table_path_not_string(self, helpers):
        config = RootConfig({'shell': {'path': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> path
                  must be a string"""
            ),
        ):
            _ = config.shell.path

    def test_table_args_not_array(self, helpers):
        config = RootConfig({'shell': {'args': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> args
                  must be an array"""
            ),
        ):
            _ = config.shell.args

    def test_table_args_entry_not_string(self, helpers):
        config = RootConfig({'shell': {'args': [9000]}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> args -> 1
                  must be a string"""
            ),
        ):
            _ = config.shell.args

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.shell = 9000
        assert config.raw_data == {'shell': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell
                  must be a string or table"""
            ),
        ):
            _ = config.shell

    def test_table_name_set_lazy_error(self, helpers):
        config = RootConfig({'shell': {}})

        config.shell.name = 9000
        assert config.raw_data == {'shell': {'name': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> name
                  must be a string"""
            ),
        ):
            _ = config.shell.name

    def test_table_path_set_lazy_error(self, helpers):
        config = RootConfig({'shell': {'name': 'foo'}})

        config.shell.path = 9000
        assert config.raw_data == {'shell': {'name': 'foo', 'path': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> path
                  must be a string"""
            ),
        ):
            _ = config.shell.path

    def test_table_args_set_lazy_error(self, helpers):
        config = RootConfig({'shell': {'name': 'foo'}})

        config.shell.args = 9000
        assert config.raw_data == {'shell': {'name': 'foo', 'args': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                shell -> args
                  must be an array"""
            ),
        ):
            _ = config.shell.args


class TestDirs:
    def test_default(self, default_cache_dir, default_data_dir):
        config = RootConfig({})

        default_cache_directory = str(default_cache_dir)
        default_data_directory = str(default_data_dir)
        assert config.dirs.project == config.dirs.project == []
        assert config.dirs.env == config.dirs.env == {}
        assert config.dirs.python == config.dirs.python == 'isolated'
        assert config.dirs.cache == config.dirs.cache == default_cache_directory
        assert config.dirs.data == config.dirs.data == default_data_directory
        assert config.raw_data == {
            'dirs': {
                'project': [],
                'env': {},
                'python': 'isolated',
                'data': default_data_directory,
                'cache': default_cache_directory,
            },
        }

    def test_not_table(self, helpers):
        config = RootConfig({'dirs': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs
                  must be a table"""
            ),
        ):
            _ = config.dirs

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.dirs = 9000
        assert config.raw_data == {'dirs': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs
                  must be a table"""
            ),
        ):
            _ = config.dirs

    def test_project(self):
        config = RootConfig({'dirs': {'project': ['foo']}})

        assert config.dirs.project == ['foo']
        assert config.raw_data == {'dirs': {'project': ['foo']}}

    def test_project_not_array(self, helpers):
        config = RootConfig({'dirs': {'project': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> project
                  must be an array"""
            ),
        ):
            _ = config.dirs.project

    def test_project_entry_not_string(self, helpers):
        config = RootConfig({'dirs': {'project': [9000]}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> project -> 1
                  must be a string"""
            ),
        ):
            _ = config.dirs.project

    def test_project_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.dirs.project = 9000
        assert config.raw_data == {'dirs': {'project': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> project
                  must be an array"""
            ),
        ):
            _ = config.dirs.project

    def test_env(self):
        config = RootConfig({'dirs': {'env': {'foo': 'bar'}}})

        assert config.dirs.env == {'foo': 'bar'}
        assert config.raw_data == {'dirs': {'env': {'foo': 'bar'}}}

    def test_env_not_table(self, helpers):
        config = RootConfig({'dirs': {'env': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> env
                  must be a table"""
            ),
        ):
            _ = config.dirs.env

    def test_env_value_not_string(self, helpers):
        config = RootConfig({'dirs': {'env': {'foo': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> env -> foo
                  must be a string"""
            ),
        ):
            _ = config.dirs.env

    def test_env_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.dirs.env = 9000
        assert config.raw_data == {'dirs': {'env': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> env
                  must be a table"""
            ),
        ):
            _ = config.dirs.env

    def test_python(self):
        config = RootConfig({'dirs': {'python': 'foo'}})

        assert config.dirs.python == 'foo'
        assert config.raw_data == {'dirs': {'python': 'foo'}}

    def test_python_not_string(self, helpers):
        config = RootConfig({'dirs': {'python': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> python
                  must be a string"""
            ),
        ):
            _ = config.dirs.python

    def test_python_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.dirs.python = 9000
        assert config.raw_data == {'dirs': {'python': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> python
                  must be a string"""
            ),
        ):
            _ = config.dirs.python

    def test_data(self):
        config = RootConfig({'dirs': {'data': 'foo'}})

        assert config.dirs.data == 'foo'
        assert config.raw_data == {'dirs': {'data': 'foo'}}

    def test_data_not_string(self, helpers):
        config = RootConfig({'dirs': {'data': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> data
                  must be a string"""
            ),
        ):
            _ = config.dirs.data

    def test_data_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.dirs.data = 9000
        assert config.raw_data == {'dirs': {'data': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> data
                  must be a string"""
            ),
        ):
            _ = config.dirs.data

    def test_cache(self):
        config = RootConfig({'dirs': {'cache': 'foo'}})

        assert config.dirs.cache == 'foo'
        assert config.raw_data == {'dirs': {'cache': 'foo'}}

    def test_cache_not_string(self, helpers):
        config = RootConfig({'dirs': {'cache': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> cache
                  must be a string"""
            ),
        ):
            _ = config.dirs.cache

    def test_cache_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.dirs.cache = 9000
        assert config.raw_data == {'dirs': {'cache': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                dirs -> cache
                  must be a string"""
            ),
        ):
            _ = config.dirs.cache


class TestProjects:
    def test_default(self):
        config = RootConfig({})

        assert config.projects == config.projects == {}
        assert config.raw_data == {'projects': {}}

    def test_not_table(self, helpers):
        config = RootConfig({'projects': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                projects
                  must be a table"""
            ),
        ):
            _ = config.projects

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.projects = 9000
        assert config.raw_data == {'projects': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                projects
                  must be a table"""
            ),
        ):
            _ = config.projects

    def test_entry_invalid_type(self, helpers):
        config = RootConfig({'projects': {'foo': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                projects -> foo
                  must be a string or table"""
            ),
        ):
            _ = config.projects

    def test_string(self):
        config = RootConfig({'projects': {'foo': 'bar'}})

        project = config.projects['foo']
        assert project.location == project.location == 'bar'
        assert config.raw_data == {'projects': {'foo': 'bar'}}

    def test_table(self):
        config = RootConfig({'projects': {'foo': {'location': 'bar'}}})

        project = config.projects['foo']
        assert project.location == project.location == 'bar'
        assert config.raw_data == {'projects': {'foo': {'location': 'bar'}}}

    def test_table_no_location(self, helpers):
        config = RootConfig({'projects': {'foo': {}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                projects -> foo -> location
                  required field"""
            ),
        ):
            _ = config.projects['foo'].location

    def test_location_not_string(self, helpers):
        config = RootConfig({'projects': {'foo': {'location': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                projects -> foo -> location
                  must be a string"""
            ),
        ):
            _ = config.projects['foo'].location

    def test_location_set_lazy_error(self, helpers):
        config = RootConfig({'projects': {'foo': {}}})

        project = config.projects['foo']
        project.location = 9000

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                projects -> foo -> location
                  must be a string"""
            ),
        ):
            _ = project.location


class TestPublish:
    def test_default(self):
        config = RootConfig({})

        assert config.publish == config.publish == {'index': {'repo': 'main'}}
        assert config.raw_data == {'publish': {'index': {'repo': 'main'}}}

    def test_defined(self):
        config = RootConfig({'publish': {'foo': {'username': '', 'password': ''}}})

        assert config.publish == {'foo': {'username': '', 'password': ''}}
        assert config.raw_data == {'publish': {'foo': {'username': '', 'password': ''}}}

    def test_not_table(self, helpers):
        config = RootConfig({'publish': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                publish
                  must be a table"""
            ),
        ):
            _ = config.publish

    def test_data_not_table(self, helpers):
        config = RootConfig({'publish': {'foo': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                publish -> foo
                  must be a table"""
            ),
        ):
            _ = config.publish

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.publish = 9000
        assert config.raw_data == {'publish': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                publish
                  must be a table"""
            ),
        ):
            _ = config.publish


class TestTemplate:
    def test_not_table(self, helpers):
        config = RootConfig({'template': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template
                  must be a table"""
            ),
        ):
            _ = config.template

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template = 9000
        assert config.raw_data == {'template': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template
                  must be a table"""
            ),
        ):
            _ = config.template

    def test_name(self):
        config = RootConfig({'template': {'name': 'foo'}})

        assert config.template.name == config.template.name == 'foo'
        assert config.raw_data == {'template': {'name': 'foo'}}

    def test_name_default_env_var(self):
        config = RootConfig({})

        assert config.template.name == 'Foo Bar'
        assert config.raw_data == {'template': {'name': 'Foo Bar'}}

    def test_name_default_git(self, temp_dir):
        config = RootConfig({})

        with temp_dir.as_cwd(exclude=['GIT_AUTHOR_NAME']):
            subprocess.check_output(['git', 'init'])
            subprocess.check_output(['git', 'config', '--local', 'user.name', 'test'])

            assert config.template.name == 'test'
            assert config.raw_data == {'template': {'name': 'test'}}

    def test_name_default_no_git(self, temp_dir):
        config = RootConfig({})

        with temp_dir.as_cwd(exclude=['*']):
            assert config.template.name == 'U.N. Owen'
            assert config.raw_data == {'template': {'name': 'U.N. Owen'}}

    def test_name_not_string(self, helpers):
        config = RootConfig({'template': {'name': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> name
                  must be a string"""
            ),
        ):
            _ = config.template.name

    def test_name_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template.name = 9000
        assert config.raw_data == {'template': {'name': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> name
                  must be a string"""
            ),
        ):
            _ = config.template.name

    def test_email(self):
        config = RootConfig({'template': {'email': 'foo'}})

        assert config.template.email == config.template.email == 'foo'
        assert config.raw_data == {'template': {'email': 'foo'}}

    def test_email_default_env_var(self):
        config = RootConfig({})

        assert config.template.email == 'foo@bar.baz'
        assert config.raw_data == {'template': {'email': 'foo@bar.baz'}}

    def test_email_default_git(self, temp_dir):
        config = RootConfig({})

        with temp_dir.as_cwd(exclude=['GIT_AUTHOR_EMAIL']):
            subprocess.check_output(['git', 'init'])
            subprocess.check_output(['git', 'config', '--local', 'user.email', 'test'])

            assert config.template.email == 'test'
            assert config.raw_data == {'template': {'email': 'test'}}

    def test_email_default_no_git(self, temp_dir):
        config = RootConfig({})

        with temp_dir.as_cwd(exclude=['*']):
            assert config.template.email == 'void@some.where'
            assert config.raw_data == {'template': {'email': 'void@some.where'}}

    def test_email_not_string(self, helpers):
        config = RootConfig({'template': {'email': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> email
                  must be a string"""
            ),
        ):
            _ = config.template.email

    def test_email_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template.email = 9000
        assert config.raw_data == {'template': {'email': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> email
                  must be a string"""
            ),
        ):
            _ = config.template.email

    def test_licenses_not_table(self, helpers):
        config = RootConfig({'template': {'licenses': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses
                  must be a table"""
            ),
        ):
            _ = config.template.licenses

    def test_licenses_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template.licenses = 9000
        assert config.raw_data == {'template': {'licenses': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses
                  must be a table"""
            ),
        ):
            _ = config.template.licenses

    def test_licenses_headers(self):
        config = RootConfig({'template': {'licenses': {'headers': False}}})

        assert config.template.licenses.headers is config.template.licenses.headers is False
        assert config.raw_data == {'template': {'licenses': {'headers': False}}}

    def test_licenses_headers_default(self):
        config = RootConfig({})

        assert config.template.licenses.headers is config.template.licenses.headers is True
        assert config.raw_data == {'template': {'licenses': {'headers': True}}}

    def test_licenses_headers_not_boolean(self, helpers):
        config = RootConfig({'template': {'licenses': {'headers': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses -> headers
                  must be a boolean"""
            ),
        ):
            _ = config.template.licenses.headers

    def test_licenses_headers_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template.licenses.headers = 9000
        assert config.raw_data == {'template': {'licenses': {'headers': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses -> headers
                  must be a boolean"""
            ),
        ):
            _ = config.template.licenses.headers

    def test_licenses_default(self):
        config = RootConfig({'template': {'licenses': {'default': ['Apache-2.0', 'MIT']}}})

        assert config.template.licenses.default == config.template.licenses.default == ['Apache-2.0', 'MIT']
        assert config.raw_data == {'template': {'licenses': {'default': ['Apache-2.0', 'MIT']}}}

    def test_licenses_default_default(self):
        config = RootConfig({})

        assert config.template.licenses.default == ['MIT']
        assert config.raw_data == {'template': {'licenses': {'default': ['MIT']}}}

    def test_licenses_default_not_array(self, helpers):
        config = RootConfig({'template': {'licenses': {'default': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses -> default
                  must be an array"""
            ),
        ):
            _ = config.template.licenses.default

    def test_licenses_default_entry_not_string(self, helpers):
        config = RootConfig({'template': {'licenses': {'default': [9000]}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses -> default -> 1
                  must be a string"""
            ),
        ):
            _ = config.template.licenses.default

    def test_licenses_default_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template.licenses.default = 9000
        assert config.raw_data == {'template': {'licenses': {'default': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> licenses -> default
                  must be an array"""
            ),
        ):
            _ = config.template.licenses.default

    def test_plugins(self):
        config = RootConfig({'template': {'plugins': {'foo': {'bar': 'baz'}}}})

        assert config.template.plugins == config.template.plugins == {'foo': {'bar': 'baz'}}
        assert config.raw_data == {'template': {'plugins': {'foo': {'bar': 'baz'}}}}

    def test_plugins_default(self):
        config = RootConfig({})

        assert config.template.plugins == {'default': {'ci': False, 'src-layout': True, 'tests': True}}
        assert config.raw_data == {
            'template': {'plugins': {'default': {'ci': False, 'src-layout': True, 'tests': True}}}
        }

    def test_plugins_not_table(self, helpers):
        config = RootConfig({'template': {'plugins': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> plugins
                  must be a table"""
            ),
        ):
            _ = config.template.plugins

    def test_plugins_data_not_table(self, helpers):
        config = RootConfig({'template': {'plugins': {'foo': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> plugins -> foo
                  must be a table"""
            ),
        ):
            _ = config.template.plugins

    def test_plugins_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.template.plugins = 9000
        assert config.raw_data == {'template': {'plugins': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                template -> plugins
                  must be a table"""
            ),
        ):
            _ = config.template.plugins


class TestTerminal:
    def test_default(self):
        config = RootConfig({})

        assert config.terminal.styles.info == config.terminal.styles.info == 'bold'
        assert config.terminal.styles.success == config.terminal.styles.success == 'bold cyan'
        assert config.terminal.styles.error == config.terminal.styles.error == 'bold red'
        assert config.terminal.styles.warning == config.terminal.styles.warning == 'bold yellow'
        assert config.terminal.styles.waiting == config.terminal.styles.waiting == 'bold magenta'
        assert config.terminal.styles.debug == config.terminal.styles.debug == 'bold'
        assert config.terminal.styles.spinner == config.terminal.styles.spinner == 'simpleDotsScrolling'
        assert config.raw_data == {
            'terminal': {
                'styles': {
                    'info': 'bold',
                    'success': 'bold cyan',
                    'error': 'bold red',
                    'warning': 'bold yellow',
                    'waiting': 'bold magenta',
                    'debug': 'bold',
                    'spinner': 'simpleDotsScrolling',
                },
            },
        }

    def test_not_table(self, helpers):
        config = RootConfig({'terminal': 9000})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal
                  must be a table"""
            ),
        ):
            _ = config.terminal

    def test_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal = 9000
        assert config.raw_data == {'terminal': 9000}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal
                  must be a table"""
            ),
        ):
            _ = config.terminal

    def test_styles_not_table(self, helpers):
        config = RootConfig({'terminal': {'styles': 9000}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles
                  must be a table"""
            ),
        ):
            _ = config.terminal.styles

    def test_styles_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles = 9000
        assert config.raw_data == {'terminal': {'styles': 9000}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles
                  must be a table"""
            ),
        ):
            _ = config.terminal.styles

    def test_styles_info(self):
        config = RootConfig({'terminal': {'styles': {'info': 'foo'}}})

        assert config.terminal.styles.info == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'info': 'foo'}}}

    def test_styles_info_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'info': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> info
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.info

    def test_styles_info_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.info = 9000
        assert config.raw_data == {'terminal': {'styles': {'info': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> info
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.info

    def test_styles_success(self):
        config = RootConfig({'terminal': {'styles': {'success': 'foo'}}})

        assert config.terminal.styles.success == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'success': 'foo'}}}

    def test_styles_success_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'success': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> success
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.success

    def test_styles_success_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.success = 9000
        assert config.raw_data == {'terminal': {'styles': {'success': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> success
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.success

    def test_styles_error(self):
        config = RootConfig({'terminal': {'styles': {'error': 'foo'}}})

        assert config.terminal.styles.error == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'error': 'foo'}}}

    def test_styles_error_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'error': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> error
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.error

    def test_styles_error_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.error = 9000
        assert config.raw_data == {'terminal': {'styles': {'error': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> error
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.error

    def test_styles_warning(self):
        config = RootConfig({'terminal': {'styles': {'warning': 'foo'}}})

        assert config.terminal.styles.warning == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'warning': 'foo'}}}

    def test_styles_warning_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'warning': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> warning
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.warning

    def test_styles_warning_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.warning = 9000
        assert config.raw_data == {'terminal': {'styles': {'warning': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> warning
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.warning

    def test_styles_waiting(self):
        config = RootConfig({'terminal': {'styles': {'waiting': 'foo'}}})

        assert config.terminal.styles.waiting == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'waiting': 'foo'}}}

    def test_styles_waiting_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'waiting': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> waiting
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.waiting

    def test_styles_waiting_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.waiting = 9000
        assert config.raw_data == {'terminal': {'styles': {'waiting': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> waiting
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.waiting

    def test_styles_debug(self):
        config = RootConfig({'terminal': {'styles': {'debug': 'foo'}}})

        assert config.terminal.styles.debug == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'debug': 'foo'}}}

    def test_styles_debug_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'debug': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> debug
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.debug

    def test_styles_debug_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.debug = 9000
        assert config.raw_data == {'terminal': {'styles': {'debug': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> debug
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.debug

    def test_styles_spinner(self):
        config = RootConfig({'terminal': {'styles': {'spinner': 'foo'}}})

        assert config.terminal.styles.spinner == 'foo'
        assert config.raw_data == {'terminal': {'styles': {'spinner': 'foo'}}}

    def test_styles_spinner_not_string(self, helpers):
        config = RootConfig({'terminal': {'styles': {'spinner': 9000}}})

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> spinner
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.spinner

    def test_styles_spinner_set_lazy_error(self, helpers):
        config = RootConfig({})

        config.terminal.styles.spinner = 9000
        assert config.raw_data == {'terminal': {'styles': {'spinner': 9000}}}

        with pytest.raises(
            ConfigurationError,
            match=helpers.dedent(
                """
                Error parsing config:
                terminal -> styles -> spinner
                  must be a string"""
            ),
        ):
            _ = config.terminal.styles.spinner
