#    Copyright (c) 2015 Telefonica I+D
#
#    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 fixtures
import mock

from muranoagent import bunch
from muranoagent.common import config as cfg
from muranoagent import exceptions as ex
from muranoagent.executors import puppet
from muranoagent.tests.unit import base
from muranoagent.tests.unit import execution_plan as ep

CONF = cfg.CONF


class TestPuppetExecutor(base.MuranoAgentTestCase, fixtures.TestWithFixtures):
    def setUp(self):
        super(TestPuppetExecutor, self).setUp()
        self.puppet_executor = puppet.PuppetExecutor('module::recipe')

    def test_create_manifest(self):
        node = self.puppet_executor._create_manifest('cookbook', 'recipe')
        self.assertEqual(node, self.get_manifest('cookbook', 'recipe'))

    def test_create_manifest_norecipe(self):
        node = self.puppet_executor._create_manifest('cookbook', '')
        self.assertEqual(node, self.get_manifest_norecipe('cookbook'))

    def test_create_hierdata(self):
        atts = {
            'att1': 'value1',
            'att2': 'value2'
        }

        node = self.puppet_executor._create_hiera_data('cookbook', atts)
        self.assertEqual(node, self.get_hieradata())

    @mock.patch('six.moves.builtins.open')
    def test_generate_files(self, open_mock):
        self._open_mock(open_mock)
        atts = {
            'att1': 'value1',
            'att2': 'value2'
        }

        self.puppet_executor._generate_files('cookbook', 'recipe', atts)

    @mock.patch('six.moves.builtins.open')
    def test_configure_puppet(self, open_mock):
        self._open_mock(open_mock)
        self.puppet_executor._configure_puppet()

    @mock.patch('subprocess.Popen')
    @mock.patch('six.moves.builtins.open')
    def test_module(self, open_mock, mock_subproc_popen):
        #
        # setup
        #
        self._open_mock(open_mock)

        process_mock = mock.Mock()
        attrs = {'communicate.return_value': ('ouput', 'ok'),
                 'poll.return_value': 0}
        process_mock.configure_mock(**attrs)
        mock_subproc_popen.return_value = process_mock

        template = self.useFixture(ep.PuppetExPlanDownloable()).execution_plan
        script = list(template['Scripts'].values())[0]
        self.puppet_executor.load('path',
                                  script['Options'])
        self.puppet_executor.run('test')

    @mock.patch('subprocess.Popen')
    @mock.patch('six.moves.builtins.open')
    def test_module_error(self, open_mock, mock_subproc_popen):
        #
        # setup
        #
        self._open_mock(open_mock)

        process_mock = mock.Mock()
        attrs = {'communicate.return_value': ('ouput', 'error'),
                 'poll.return_value': 2}
        process_mock.configure_mock(**attrs)
        mock_subproc_popen.return_value = process_mock

        template = self.useFixture(ep.PuppetExPlanDownloable()).execution_plan
        script = list(template['Scripts'].values())[0]
        self.puppet_executor.load('path',
                                  script['Options'])
        self.assertRaises(ex.CustomException, self.puppet_executor.run,
                          'test')

    def test_puppet_module_wrong(self):
        puppet_executor = puppet.PuppetExecutor('wrong')
        self.assertRaises(ex.CustomException, puppet_executor.run,
                          'test')

    def _stub_uuid(self, values=None):
        class FakeUUID(object):
            def __init__(self, v):
                self.hex = v

        if values is None:
            values = []
        mock_uuid4 = mock.patch('uuid.uuid4').start()
        mock_uuid4.side_effect = [FakeUUID(v) for v in values]
        return mock_uuid4

    def _open_mock(self, open_mock):
        context_manager_mock = mock.Mock()
        open_mock.return_value = context_manager_mock
        file_mock = mock.Mock()
        file_mock.read.return_value = ''
        enter_mock = mock.Mock()
        enter_mock.return_value = file_mock
        exit_mock = mock.Mock()
        setattr(context_manager_mock, '__enter__', enter_mock)
        setattr(context_manager_mock, '__exit__', exit_mock)

    def get_hieradata(self):
        return {'cookbook::att1': 'value1', 'cookbook::att2': 'value2'}

    def get_manifest(self, cookbook, recipe):
        return "node \'default\' { " \
               "class { " + cookbook + '::' + recipe + ':}}'

    def get_manifest_norecipe(self, cookbook):
        return "node \'default\' { " \
               "class { " + cookbook + ':}}'
