#
#    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 mock
import six

from heat.common import grouputils
from heat.common import identifier
from heat.common import template_format
from heat.rpc import client as rpc_client
from heat.tests import common
from heat.tests import utils

nested_stack = '''
heat_template_version: 2013-05-23
resources:
  r0:
    type: OverwrittenFnGetRefIdType
  r1:
    type: OverwrittenFnGetRefIdType
'''


class GroupUtilsTest(common.HeatTestCase):

    def test_non_nested_resource(self):
        group = mock.Mock()
        group.nested_identifier.return_value = None
        group.nested.return_value = None

        self.assertEqual(0, grouputils.get_size(group))
        self.assertEqual([], grouputils.get_members(group))
        self.assertEqual([], grouputils.get_member_refids(group))
        self.assertEqual([], grouputils.get_member_names(group))

    def test_normal_group(self):
        group = mock.Mock()
        t = template_format.parse(nested_stack)
        stack = utils.parse_stack(t)
        group.nested.return_value = stack

        # member list (sorted)
        members = [r for r in six.itervalues(stack)]
        expected = sorted(members, key=lambda r: (r.created_time, r.name))
        actual = grouputils.get_members(group)
        self.assertEqual(expected, actual)

        # refids
        actual_ids = grouputils.get_member_refids(group)
        self.assertEqual(['ID-r0', 'ID-r1'], actual_ids)
        partial_ids = grouputils.get_member_refids(group, exclude=['ID-r1'])
        self.assertEqual(['ID-r0'], partial_ids)

    def test_group_with_failed_members(self):
        group = mock.Mock()
        t = template_format.parse(nested_stack)
        stack = utils.parse_stack(t)
        self.patchobject(group, 'nested', return_value=stack)

        # Just failed for whatever reason
        rsrc_err = stack.resources['r0']
        rsrc_err.status = rsrc_err.FAILED
        rsrc_ok = stack.resources['r1']

        self.assertEqual([rsrc_ok], grouputils.get_members(group))
        self.assertEqual(['ID-r1'], grouputils.get_member_refids(group))


class GroupInspectorTest(common.HeatTestCase):
    resources = [
        {
            'updated_time': '2018-01-01T12:00',
            'creation_time': '2018-01-01T02:00',
            'resource_name': 'A',
            'physical_resource_id': 'a',
            'resource_action': 'UPDATE',
            'resource_status': 'COMPLETE',
            'resource_status_reason': 'resource changed',
            'resource_type': 'OS::Heat::Test',
            'resource_id': 'aaaaaaaa',
            'stack_identity': 'bar',
            'stack_name': 'nested_test',
            'required_by': [],
            'parent_resource': 'stack_resource',
        },
        {
            'updated_time': '2018-01-01T10:00',
            'creation_time': '2018-01-01T03:00',
            'resource_name': 'E',
            'physical_resource_id': 'e',
            'resource_action': 'UPDATE',
            'resource_status': 'FAILED',
            'resource_status_reason': 'reasons',
            'resource_type': 'OS::Heat::Test',
            'resource_id': 'eeeeeeee',
            'stack_identity': 'bar',
            'stack_name': 'nested_test',
            'required_by': [],
            'parent_resource': 'stack_resource',
        },
        {
            'updated_time': '2018-01-01T11:00',
            'creation_time': '2018-01-01T03:00',
            'resource_name': 'B',
            'physical_resource_id': 'b',
            'resource_action': 'UPDATE',
            'resource_status': 'FAILED',
            'resource_status_reason': 'reasons',
            'resource_type': 'OS::Heat::Test',
            'resource_id': 'bbbbbbbb',
            'stack_identity': 'bar',
            'stack_name': 'nested_test',
            'required_by': [],
            'parent_resource': 'stack_resource',
        },
        {
            'updated_time': '2018-01-01T13:00',
            'creation_time': '2018-01-01T01:00',
            'resource_name': 'C',
            'physical_resource_id': 'c',
            'resource_action': 'UPDATE',
            'resource_status': 'COMPLETE',
            'resource_status_reason': 'resource changed',
            'resource_type': 'OS::Heat::Test',
            'resource_id': 'cccccccc',
            'stack_identity': 'bar',
            'stack_name': 'nested_test',
            'required_by': [],
            'parent_resource': 'stack_resource',
        },
        {
            'updated_time': '2018-01-01T04:00',
            'creation_time': '2018-01-01T04:00',
            'resource_name': 'F',
            'physical_resource_id': 'f',
            'resource_action': 'CREATE',
            'resource_status': 'COMPLETE',
            'resource_status_reason': 'resource changed',
            'resource_type': 'OS::Heat::Test',
            'resource_id': 'ffffffff',
            'stack_identity': 'bar',
            'stack_name': 'nested_test',
            'required_by': [],
            'parent_resource': 'stack_resource',
        },
        {
            'updated_time': '2018-01-01T04:00',
            'creation_time': '2018-01-01T04:00',
            'resource_name': 'D',
            'physical_resource_id': 'd',
            'resource_action': 'CREATE',
            'resource_status': 'COMPLETE',
            'resource_status_reason': 'resource changed',
            'resource_type': 'OS::Heat::Test',
            'resource_id': 'dddddddd',
            'stack_identity': 'bar',
            'stack_name': 'nested_test',
            'required_by': [],
            'parent_resource': 'stack_resource',
        },
    ]

    template = {
        'heat_template_version': 'newton',
        'resources': {
            'A': {
                'type': 'OS::Heat::TestResource',
            },
        },
    }

    def setUp(self):
        super(GroupInspectorTest, self).setUp()
        self.ctx = mock.Mock()
        self.rpc_client = mock.Mock(spec=rpc_client.EngineClient)
        self.identity = identifier.HeatIdentifier('foo', 'nested_test', 'bar')

        self.list_rsrcs = self.rpc_client.list_stack_resources
        self.get_tmpl = self.rpc_client.get_template

        self.insp = grouputils.GroupInspector(self.ctx, self.rpc_client,
                                              self.identity)

    def test_no_identity(self):
        self.insp = grouputils.GroupInspector(self.ctx, self.rpc_client, None)

        self.assertEqual(0, self.insp.size(include_failed=True))
        self.assertEqual([], list(self.insp.member_names(include_failed=True)))
        self.assertIsNone(self.insp.template())

        self.list_rsrcs.assert_not_called()
        self.get_tmpl.assert_not_called()

    def test_size_include_failed(self):
        self.list_rsrcs.return_value = self.resources

        self.assertEqual(6, self.insp.size(include_failed=True))

        self.list_rsrcs.assert_called_once_with(self.ctx, dict(self.identity))

    def test_size_exclude_failed(self):
        self.list_rsrcs.return_value = self.resources

        self.assertEqual(4, self.insp.size(include_failed=False))

        self.list_rsrcs.assert_called_once_with(self.ctx, dict(self.identity))

    def test_member_names_include_failed(self):
        self.list_rsrcs.return_value = self.resources

        self.assertEqual(['B', 'E', 'C', 'A', 'D', 'F'],
                         list(self.insp.member_names(include_failed=True)))

        self.list_rsrcs.assert_called_once_with(self.ctx, dict(self.identity))

    def test_member_names_exclude_failed(self):
        self.list_rsrcs.return_value = self.resources

        self.assertEqual(['C', 'A', 'D', 'F'],
                         list(self.insp.member_names(include_failed=False)))

        self.list_rsrcs.assert_called_once_with(self.ctx, dict(self.identity))

    def test_list_rsrc_caching(self):
        self.list_rsrcs.return_value = self.resources

        self.insp.size(include_failed=False)
        list(self.insp.member_names(include_failed=True))
        self.insp.size(include_failed=True)
        list(self.insp.member_names(include_failed=False))

        self.list_rsrcs.assert_called_once_with(self.ctx, dict(self.identity))
        self.get_tmpl.assert_not_called()

    def test_get_template(self):
        self.get_tmpl.return_value = self.template

        tmpl = self.insp.template()
        self.assertEqual(self.template, tmpl.t)

        self.get_tmpl.assert_called_once_with(self.ctx, dict(self.identity))

    def test_get_tmpl_caching(self):
        self.get_tmpl.return_value = self.template

        self.insp.template()
        self.insp.template()

        self.get_tmpl.assert_called_once_with(self.ctx, dict(self.identity))
        self.list_rsrcs.assert_not_called()
