# -*- coding: utf-8 -*-

"""Tests for `cookiecutter.prompt` module."""

import platform
from collections import OrderedDict

import pytest
import six

import cookiecutter.environment
from cookiecutter import prompt, exceptions, environment


@pytest.fixture(autouse=True)
def patch_readline_on_win(monkeypatch):
    """Fixture. Overwrite windows end of line to linux standard."""
    if 'windows' in platform.platform().lower():
        monkeypatch.setattr('sys.stdin.readline', lambda: '\n')


class TestRenderVariable:
    """Class to unite simple and complex tests for render_variable function."""

    @pytest.mark.parametrize(
        'raw_var, rendered_var',
        [
            (1, '1'),
            (True, 'True'),
            ('foo', 'foo'),
            ('{{cookiecutter.project}}', 'foobar'),
            (None, None),
        ],
    )
    def test_convert_to_str(self, mocker, raw_var, rendered_var):
        """Verify simple items correctly rendered to strings."""
        env = environment.StrictEnvironment()
        from_string = mocker.patch(
            'cookiecutter.prompt.StrictEnvironment.from_string', wraps=env.from_string
        )
        context = {'project': 'foobar'}

        result = cookiecutter.environment.render_variable(
            env, raw_var, context, context_key='cookiecutter'
        )
        assert result == rendered_var

        # Make sure that non None non str variables are converted beforehand
        if raw_var is not None:
            if not isinstance(raw_var, six.string_types):
                raw_var = str(raw_var)
            from_string.assert_called_once_with(raw_var)
        else:
            assert not from_string.called

    @pytest.mark.parametrize(
        'raw_var, rendered_var',
        [
            ({1: True, 'foo': False}, {'1': 'True', 'foo': 'False'}),
            (
                {'{{cookiecutter.project}}': ['foo', 1], 'bar': False},
                {'foobar': ['foo', '1'], 'bar': 'False'},
            ),
            (['foo', '{{cookiecutter.project}}', None], ['foo', 'foobar', None]),
        ],
    )
    def test_convert_to_str_complex_variables(self, raw_var, rendered_var):
        """Verify tree items correctly rendered."""
        env = environment.StrictEnvironment()
        context = {'project': 'foobar'}

        result = cookiecutter.environment.render_variable(
            env, raw_var, context, context_key='cookiecutter'
        )
        assert result == rendered_var


class TestPrompt(object):
    """Class to unite user prompt related tests."""

    @pytest.mark.parametrize(
        'context',
        [
            {'cookiecutter': {'full_name': 'Your Name'}},
            {'cookiecutter': {'full_name': u'Řekni či napiš své jméno'}},
        ],
        ids=['ASCII default prompt/input', 'Unicode default prompt/input'],
    )
    def test_prompt_for_config(self, monkeypatch, context):
        """Verify `prompt_for_config` call `read_user_variable` on text request."""
        monkeypatch.setattr(
            'cookiecutter.prompt.read_user_variable', lambda var, default: default,
        )

        cookiecutter_dict = prompt.prompt_for_config(context)
        assert cookiecutter_dict == context['cookiecutter']

    def test_prompt_for_config_dict(self, monkeypatch):
        """Verify `prompt_for_config` call `read_user_variable` on dict request."""
        monkeypatch.setattr(
            'cookiecutter.prompt.read_user_dict',
            lambda var, default: {"key": "value", "integer": 37},
        )
        context = {'cookiecutter': {'details': {}}}

        cookiecutter_dict = prompt.prompt_for_config(context)
        assert cookiecutter_dict == {'details': {'key': u'value', 'integer': 37}}

    def test_should_render_dict(self):
        """Verify template inside dictionary variable rendered."""
        context = {
            'cookiecutter': {
                'project_name': 'Slartibartfast',
                'details': {
                    '{{cookiecutter.project_name}}': '{{cookiecutter.project_name}}'
                },
            }
        }

        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
        assert cookiecutter_dict == {
            'project_name': 'Slartibartfast',
            'details': {'Slartibartfast': u'Slartibartfast'},
        }

    def test_should_render_deep_dict(self):
        """Verify nested structures like dict in dict, rendered correctly."""
        context = {
            'cookiecutter': {
                'project_name': "Slartibartfast",
                'details': {
                    "key": "value",
                    "integer_key": 37,
                    "other_name": '{{cookiecutter.project_name}}',
                    "dict_key": {
                        "deep_key": "deep_value",
                        "deep_integer": 42,
                        "deep_other_name": '{{cookiecutter.project_name}}',
                        "deep_list": [
                            "deep value 1",
                            "{{cookiecutter.project_name}}",
                            "deep value 3",
                        ],
                    },
                    "list_key": [
                        "value 1",
                        "{{cookiecutter.project_name}}",
                        "value 3",
                    ],
                },
            }
        }

        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
        assert cookiecutter_dict == {
            'project_name': "Slartibartfast",
            'details': {
                "key": "value",
                "integer_key": "37",
                "other_name": "Slartibartfast",
                "dict_key": {
                    "deep_key": "deep_value",
                    "deep_integer": "42",
                    "deep_other_name": "Slartibartfast",
                    "deep_list": ["deep value 1", "Slartibartfast", "deep value 3"],
                },
                "list_key": ["value 1", "Slartibartfast", "value 3"],
            },
        }

    def test_prompt_for_templated_config(self, monkeypatch):
        """Verify Jinja2 templating works in unicode prompts."""
        monkeypatch.setattr(
            'cookiecutter.prompt.read_user_variable', lambda var, default: default
        )
        context = {
            'cookiecutter': OrderedDict(
                [
                    ('project_name', u'A New Project'),
                    (
                        'pkg_name',
                        u'{{ cookiecutter.project_name|lower|replace(" ", "") }}',
                    ),
                ]
            )
        }

        exp_cookiecutter_dict = {
            'project_name': u'A New Project',
            'pkg_name': u'anewproject',
        }
        cookiecutter_dict = prompt.prompt_for_config(context)
        assert cookiecutter_dict == exp_cookiecutter_dict

    def test_dont_prompt_for_private_context_var(self, monkeypatch):
        """Verify `read_user_variable` not called for private context variables."""
        monkeypatch.setattr(
            'cookiecutter.prompt.read_user_variable',
            lambda var, default: pytest.fail(
                'Should not try to read a response for private context var'
            ),
        )
        context = {'cookiecutter': {'_copy_without_render': ['*.html']}}
        cookiecutter_dict = prompt.prompt_for_config(context)
        assert cookiecutter_dict == {'_copy_without_render': ['*.html']}

    def test_should_not_render_private_variables(self):
        """Verify private(underscored) variables not rendered by `prompt_for_config`.

        Private variables designed to be raw, same as context input.
        """
        context = {
            'cookiecutter': {
                'project_name': 'Skip render',
                '_skip_jinja_template': '{{cookiecutter.project_name}}',
                '_skip_float': 123.25,
                '_skip_integer': 123,
                '_skip_boolean': True,
                '_skip_nested': True,
            }
        }
        cookiecutter_dict = prompt.prompt_for_config(context, no_input=True)
        assert cookiecutter_dict == context['cookiecutter']


class TestReadUserChoice(object):
    """Class to unite choices prompt related tests."""

    def test_should_invoke_read_user_choice(self, mocker):
        """Verify correct function called for select(list) variables."""
        prompt_choice = mocker.patch(
            'cookiecutter.prompt.prompt_choice_for_config',
            wraps=prompt.prompt_choice_for_config,
        )

        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
        read_user_choice.return_value = 'all'

        read_user_variable = mocker.patch('cookiecutter.prompt.read_user_variable')

        choices = ['landscape', 'portrait', 'all']
        context = {'cookiecutter': {'orientation': choices}}

        cookiecutter_dict = prompt.prompt_for_config(context)

        assert not read_user_variable.called
        assert prompt_choice.called
        read_user_choice.assert_called_once_with('orientation', choices)
        assert cookiecutter_dict == {'orientation': 'all'}

    def test_should_invoke_read_user_variable(self, mocker):
        """Verify correct function called for string input variables."""
        read_user_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
        read_user_variable.return_value = u'Audrey Roy'

        prompt_choice = mocker.patch('cookiecutter.prompt.prompt_choice_for_config')

        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')

        context = {'cookiecutter': {'full_name': 'Your Name'}}

        cookiecutter_dict = prompt.prompt_for_config(context)

        assert not prompt_choice.called
        assert not read_user_choice.called
        read_user_variable.assert_called_once_with('full_name', 'Your Name')
        assert cookiecutter_dict == {'full_name': u'Audrey Roy'}

    def test_should_render_choices(self, mocker):
        """Verify Jinja2 templating engine works inside choices variables."""
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
        read_user_choice.return_value = u'anewproject'

        read_user_variable = mocker.patch('cookiecutter.prompt.read_user_variable')
        read_user_variable.return_value = u'A New Project'

        rendered_choices = [u'foo', u'anewproject', u'bar']

        context = {
            'cookiecutter': OrderedDict(
                [
                    ('project_name', u'A New Project'),
                    (
                        'pkg_name',
                        [
                            u'foo',
                            u'{{ cookiecutter.project_name|lower|replace(" ", "") }}',
                            u'bar',
                        ],
                    ),
                ]
            )
        }

        expected = {
            'project_name': u'A New Project',
            'pkg_name': u'anewproject',
        }
        cookiecutter_dict = prompt.prompt_for_config(context)

        read_user_variable.assert_called_once_with('project_name', u'A New Project')
        read_user_choice.assert_called_once_with('pkg_name', rendered_choices)
        assert cookiecutter_dict == expected


class TestPromptChoiceForConfig(object):
    """Class to unite choices prompt related tests with config test."""

    @pytest.fixture
    def choices(self):
        """Fixture. Just populate choices variable."""
        return ['landscape', 'portrait', 'all']

    @pytest.fixture
    def context(self, choices):
        """Fixture. Just populate context variable."""
        return {'cookiecutter': {'orientation': choices}}

    def test_should_return_first_option_if_no_input(self, mocker, choices, context):
        """Verify prompt_choice_for_config return first list option on no_input=True."""
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')

        expected_choice = choices[0]

        actual_choice = prompt.prompt_choice_for_config(
            cookiecutter_dict=context,
            env=environment.StrictEnvironment(),
            key='orientation',
            options=choices,
            no_input=True,  # Suppress user input
            context_key='cookiecutter',
        )

        assert not read_user_choice.called
        assert expected_choice == actual_choice

    def test_should_read_user_choice(self, mocker, choices, context):
        """Verify prompt_choice_for_config return user selection on no_input=False."""
        read_user_choice = mocker.patch('cookiecutter.prompt.read_user_choice')
        read_user_choice.return_value = 'all'

        expected_choice = 'all'

        actual_choice = prompt.prompt_choice_for_config(
            cookiecutter_dict=context,
            env=environment.StrictEnvironment(),
            key='orientation',
            options=choices,
            no_input=False,  # Ask the user for input
            context_key='cookiecutter',
        )
        read_user_choice.assert_called_once_with('orientation', choices)
        assert expected_choice == actual_choice


@pytest.mark.parametrize(
    'context',
    (
        {'cookiecutter': {'foo': '{{cookiecutter.nope}}'}},
        {'cookiecutter': {'foo': ['123', '{{cookiecutter.nope}}', '456']}},
        {'cookiecutter': {'foo': {'{{cookiecutter.nope}}': 'value'}}},
        {'cookiecutter': {'foo': {'key': '{{cookiecutter.nope}}'}}},
    ),
    ids=[
        'Undefined variable in cookiecutter dict',
        'Undefined variable in cookiecutter dict with choices',
        'Undefined variable in cookiecutter dict with dict_key',
        'Undefined variable in cookiecutter dict with key_value',
    ],
)
def test_undefined_variable(context):
    """Verify `prompt.prompt_for_config` raises correct error."""
    with pytest.raises(exceptions.UndefinedVariableInTemplate) as err:
        prompt.prompt_for_config(context, no_input=True)

    error = err.value
    assert error.message == "Unable to render variable 'foo'"
    assert error.context == context
