#
#    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
from oslo_config import cfg
from requests import exceptions
import six
import yaml

from heat.common import exception
from heat.common import identifier
from heat.common import template_format
from heat.common import urlfetch
from heat.engine import resource
from heat.engine.resources.aws.cfn import stack as stack_res
from heat.engine import rsrc_defn
from heat.engine import stack as parser
from heat.engine import template
from heat.objects import resource_data as resource_data_object
from heat.tests import common
from heat.tests import generic_resource as generic_rsrc
from heat.tests import utils


class NestedStackTest(common.HeatTestCase):
    test_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Resources:
  the_nested:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://server.test/the.template
      Parameters:
        KeyName: foo
'''

    nested_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  KeyName:
    Type: String
Outputs:
  Foo:
    Value: bar
'''

    update_template = '''
HeatTemplateFormatVersion: '2012-12-12'
Parameters:
  KeyName:
    Type: String
Outputs:
  Bar:
    Value: foo
'''

    def setUp(self):
        super(NestedStackTest, self).setUp()
        self.patchobject(urlfetch, 'get')

    def validate_stack(self, template):
        t = template_format.parse(template)
        stack = self.parse_stack(t)
        res = stack.validate()
        self.assertIsNone(res)
        return stack

    def parse_stack(self, t, data=None):
        ctx = utils.dummy_context('test_username', 'aaaa', 'password')
        stack_name = 'test_stack'
        tmpl = template.Template(t)
        stack = parser.Stack(ctx, stack_name, tmpl, adopt_stack_data=data)
        stack.store()
        return stack

    @mock.patch.object(parser.Stack, 'root_stack_id')
    @mock.patch.object(parser.Stack, 'total_resources')
    def test_nested_stack_three_deep(self, tr, rsi):
        root_template = '''
HeatTemplateFormatVersion: 2012-12-12
Resources:
    Nested:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth1.template'
'''
        depth1_template = '''
HeatTemplateFormatVersion: 2012-12-12
Resources:
    Nested:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth2.template'
'''
        depth2_template = '''
HeatTemplateFormatVersion: 2012-12-12
Resources:
    Nested:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth3.template'
            Parameters:
                KeyName: foo
'''
        urlfetch.get.side_effect = [
            depth1_template,
            depth2_template,
            self.nested_template]

        rsi.return_value = '1234'
        tr.return_value = 2

        self.validate_stack(root_template)
        calls = [mock.call('https://server.test/depth1.template'),
                 mock.call('https://server.test/depth2.template'),
                 mock.call('https://server.test/depth3.template')]
        urlfetch.get.assert_has_calls(calls)
        tr.assert_called_with('1234')

    @mock.patch.object(parser.Stack, 'root_stack_id')
    @mock.patch.object(parser.Stack, 'total_resources')
    def test_nested_stack_six_deep(self, tr, rsi):
        tmpl = '''
HeatTemplateFormatVersion: 2012-12-12
Resources:
    Nested:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth%i.template'
'''
        root_template = tmpl % 1
        depth1_template = tmpl % 2
        depth2_template = tmpl % 3
        depth3_template = tmpl % 4
        depth4_template = tmpl % 5
        depth5_template = tmpl % 6
        depth5_template += '''
            Parameters:
                KeyName: foo
'''

        urlfetch.get.side_effect = [
            depth1_template,
            depth2_template,
            depth3_template,
            depth4_template,
            depth5_template,
            self.nested_template]

        rsi.return_value = '1234'
        tr.return_value = 5

        t = template_format.parse(root_template)
        stack = self.parse_stack(t)
        res = self.assertRaises(exception.StackValidationFailed,
                                stack.validate)
        self.assertIn('Recursion depth exceeds', six.text_type(res))

        calls = [mock.call('https://server.test/depth1.template'),
                 mock.call('https://server.test/depth2.template'),
                 mock.call('https://server.test/depth3.template'),
                 mock.call('https://server.test/depth4.template'),
                 mock.call('https://server.test/depth5.template'),
                 mock.call('https://server.test/depth6.template')]
        urlfetch.get.assert_has_calls(calls)

    def test_nested_stack_four_wide(self):
        root_template = '''
HeatTemplateFormatVersion: 2012-12-12
Resources:
    Nested:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth1.template'
            Parameters:
                KeyName: foo
    Nested2:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth2.template'
            Parameters:
                KeyName: foo
    Nested3:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth3.template'
            Parameters:
                KeyName: foo
    Nested4:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/depth4.template'
            Parameters:
                KeyName: foo
'''
        urlfetch.get.return_value = self.nested_template

        self.validate_stack(root_template)
        calls = [mock.call('https://server.test/depth1.template'),
                 mock.call('https://server.test/depth2.template'),
                 mock.call('https://server.test/depth3.template'),
                 mock.call('https://server.test/depth4.template')]
        urlfetch.get.assert_has_calls(calls, any_order=True)

    @mock.patch.object(parser.Stack, 'root_stack_id')
    @mock.patch.object(parser.Stack, 'total_resources')
    def test_nested_stack_infinite_recursion(self, tr, rsi):
        tmpl = '''
HeatTemplateFormatVersion: 2012-12-12
Resources:
    Nested:
        Type: AWS::CloudFormation::Stack
        Properties:
            TemplateURL: 'https://server.test/the.template'
'''