#    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.

from heat_integrationtests.functional import functional_base


cfn_template = '''
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  env_type:
    Default: test
    Type: String
    AllowedValues: [prod, test]
  zone:
    Type: String
    Default: beijing
Conditions:
  Prod: {"Fn::Equals" : [{Ref: env_type}, "prod"]}
  Test:
    Fn::Not:
    - Fn::Equals:
      - Ref: env_type
      - prod
  Beijing_Prod:
    Fn::And:
    - Fn::Equals:
      - Ref: env_type
      - prod
    - Fn::Equals:
      - Ref: zone
      - beijing
  Xian_Zone:
    Fn::Equals:
    - Ref: zone
    - xian
  Xianyang_Zone:
    Fn::Equals:
    - Ref: zone
    - xianyang
  Fujian_Zone:
    Fn::Or:
    - Fn::Equals:
      - Ref: zone
      - fuzhou
    - Fn::Equals:
      - Ref: zone
      - xiamen
  Fujian_Prod:
    Fn::And:
    - Fujian_Zone
    - Prod
  Shannxi_Provice:
    Fn::Or:
    - Xian_Zone
    - Xianyang_Zone
  Not_Shannxi:
    Fn::Not: [Shannxi_Provice]
Resources:
  test_res:
    Type: OS::Heat::TestResource
    Properties:
      value: {"Fn::If": ["Prod", "env_is_prod", "env_is_test"]}
  prod_res:
    Type: OS::Heat::TestResource
    Properties:
      value: prod_res
    Condition: Prod
  test_res1:
    Type: OS::Heat::TestResource
    Properties:
      value: just in test env
    Condition: Test
  beijing_prod_res:
    Type: OS::Heat::TestResource
    Properties:
      value: beijing_prod_res
    Condition: Beijing_Prod
  fujian_res:
    Type: OS::Heat::TestResource
    Condition: Fujian_Zone
    Properties:
      value: fujian_res
  fujian_prod_res:
    Type: OS::Heat::TestResource
    Condition: Fujian_Prod
    Properties:
      value: fujian_prod_res
  shannxi_res:
    Type: OS::Heat::TestResource
    Condition: Shannxi_Provice
    Properties:
      value: shannxi_res
  not_shannxi_res:
    Type: OS::Heat::TestResource
    Condition: Not_Shannxi
    Properties:
      value: not_shannxi_res
Outputs:
  res_value:
    Value: {"Fn::GetAtt": [prod_res, output]}
    Condition: Prod
  test_res_value:
    Value: {"Fn::GetAtt": [test_res, output]}
  prod_resource:
    Value: {"Fn::If": [Prod, {Ref: prod_res}, 'no_prod_res']}
  test_res1_value:
    Value: {"Fn::If": [Test, {"Fn::GetAtt": [test_res1, output]},
                       'no_test_res1']}
  beijing_prod_res:
    Value: {"Fn::If": [Beijing_Prod, {Ref: beijing_prod_res}, 'no_prod_res']}
'''

hot_template = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    default: test
    type: string
    constraints:
      - allowed_values: [prod, test]
  zone:
    type: string
    default: beijing
conditions:
  prod: {equals : [{get_param: env_type}, "prod"]}
  test:
    not:
      equals:
      - get_param: env_type
      - prod
  beijing_prod:
    and:
    - equals:
      - get_param: zone
      - beijing
    - equals:
      - get_param: env_type
      - prod
  xian_zone:
    equals:
    - get_param: zone
    - xian
  xianyang_zone:
    equals:
    - get_param: zone
    - xianyang
  fujian_zone:
    or:
    - equals:
      - get_param: zone
      - fuzhou
    - equals:
      - get_param: zone
      - xiamen
  fujian_prod:
    and:
    - fujian_zone
    - prod
  shannxi_provice:
    or:
    - xian_zone
    - xianyang_zone
  not_shannxi:
    not: shannxi_provice
resources:
  test_res:
    type: OS::Heat::TestResource
    properties:
      value: {if: ["prod", "env_is_prod", "env_is_test"]}
  prod_res:
    type: OS::Heat::TestResource
    properties:
      value: prod_res
    condition: prod
  test_res1:
    type: OS::Heat::TestResource
    properties:
      value: just in test env
    condition: test
  beijing_prod_res:
    type: OS::Heat::TestResource
    properties:
      value: beijing_prod_res
    condition: beijing_prod
  fujian_res:
    type: OS::Heat::TestResource
    condition: fujian_zone
    properties:
      value: fujian_res
  fujian_prod_res:
    type: OS::Heat::TestResource
    condition: fujian_prod
    properties:
      value: fujian_prod_res
  shannxi_res:
    type: OS::Heat::TestResource
    condition: shannxi_provice
    properties:
      value: shannxi_res
  not_shannxi_res:
    type: OS::Heat::TestResource
    condition: not_shannxi
    properties:
      value: not_shannxi_res
outputs:
  res_value:
    value: {get_attr: [prod_res, output]}
    condition: prod
  test_res_value:
    value: {get_attr: [test_res, output]}
  prod_resource:
    value: {if: [prod, {get_resource: prod_res}, 'no_prod_res']}
  test_res1_value:
    value: {if: [test, {get_attr: [test_res1, output]}, 'no_test_res1']}
  beijing_prod_res:
    value: {if: [beijing_prod, {get_resource: beijing_prod_res},
                 'no_prod_res']}
'''

before_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    default: test
    type: string
conditions:
  cd1: {equals : [{get_param: env_type}, "prod"]}
resources:
  test:
    type: OS::Heat::TestResource
    properties:
      value: {if: [cd1, 'prod', 'test']}
'''

after_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    default: prod
    type: string
conditions:
  cd2: {equals : [{get_param: env_type}, "prod"]}
resources:
  test:
    type: OS::Heat::TestResource
    properties:
      value: {if: [cd2, 'prod', 'test']}
  test2:
    type: OS::Heat::TestResource
    properties:
      value: {if: [cd2, 'prod', 'test']}
'''

fail_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    default: prod
    type: string
conditions:
  cd3: {equals : [{get_param: env_type}, "prod"]}
resources:
  test:
    type: OS::Heat::TestResource
    properties:
      value: {if: [cd3, 'prod', 'test']}
  test2:
    type: OS::Heat::TestResource
    properties:
      value: {if: [cd3, 'prod', 'test']}
  test_fail:
    type: OS::Heat::TestResource
    properties:
      fail: True
    depends_on: [test, test2]
'''

recover_rename_tmpl = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    default: prod
    type: string
conditions:
  cd3: {equals : [{get_param: env_type}, "prod"]}
resources:
  test2:
    type: OS::Heat::TestResource
    properties:
      value: {if: [cd3, 'prod', 'test']}
  test_fail:
    type: OS::Heat::TestResource
    properties:
      fail: False
    depends_on: [test2]
'''


class CreateUpdateResConditionTest(functional_base.FunctionalTestsBase):

    def res_assert_for_prod(self, stack_identifier,
                            bj_prod=True, fj_zone=False,
                            shannxi_provice=False):
        def is_not_deleted(r):
            return r.resource_status != 'DELETE_COMPLETE'

        resources = self.list_resources(stack_identifier, is_not_deleted)
        res_names = set(resources)

        if bj_prod:
            self.assertEqual(4, len(resources))
            self.assertIn('beijing_prod_res', res_names)
            self.assertIn('not_shannxi_res', res_names)
        elif fj_zone:
            self.assertEqual(5, len(resources))
            self.assertIn('fujian_res', res_names)
            self.assertNotIn('beijing_prod_res', res_names)
            self.assertIn('not_shannxi_res', res_names)
            self.assertIn('fujian_prod_res', res_names)
        elif shannxi_provice:
            self.assertEqual(3, len(resources))
            self.assertIn('shannxi_res', res_names)
        else:
            self.assertEqual(3, len(resources))
            self.assertIn('not_shannxi_res', res_names)
        self.assertIn('prod_res', res_names)
        self.assertIn('test_res', res_names)

    def res_assert_for_test(self, stack_identifier, fj_zone=False,
                            shannxi_provice=False):
        def is_not_deleted(r):
            return r.resource_status != 'DELETE_COMPLETE'

        resources = self.list_resources(stack_identifier, is_not_deleted)
        res_names = set(resources)

        if fj_zone:
            self.assertEqual(4, len(resources))
            self.assertIn('fujian_res', res_names)
            self.assertIn('not_shannxi_res', res_names)
        elif shannxi_provice:
            self.assertEqual(3, len(resources))
            self.assertNotIn('fujian_res', res_names)
            self.assertIn('shannxi_res', res_names)
        else:
            self.assertEqual(3, len(resources))
            self.assertIn('not_shannxi_res', res_names)
        self.assertIn('test_res', res_names)
        self.assertIn('test_res1', res_names)
        self.assertNotIn('prod_res', res_names)

    def output_assert_for_prod(self, stack_id, bj_prod=True):
        output = self.client.stacks.output_show(stack_id,
                                                'res_value')['output']
        self.assertEqual('prod_res', output['output_value'])

        test_res_value = self.client.stacks.output_show(
            stack_id, 'test_res_value')['output']
        self.assertEqual('env_is_prod', test_res_value['output_value'])

        prod_resource = self.client.stacks.output_show(
            stack_id, 'prod_resource')['output']
        self.assertNotEqual('no_prod_res', prod_resource['output_value'])

        test_res_output = self.client.stacks.output_show(
            stack_id, 'test_res1_value')['output']
        self.assertEqual('no_test_res1', test_res_output['output_value'])

        beijing_prod_res = self.client.stacks.output_show(
            stack_id, 'beijing_prod_res')['output']
        if bj_prod:
            self.assertNotEqual('no_prod_res',
                                beijing_prod_res['output_value'])
        else:
            self.assertEqual('no_prod_res', beijing_prod_res['output_value'])

    def output_assert_for_test(self, stack_id):
        output = self.client.stacks.output_show(stack_id,
                                                'res_value')['output']
        self.assertIsNone(output['output_value'])

        test_res_value = self.client.stacks.output_show(
            stack_id, 'test_res_value')['output']
        self.assertEqual('env_is_test', test_res_value['output_value'])

        prod_resource = self.client.stacks.output_show(
            stack_id, 'prod_resource')['output']
        self.assertEqual('no_prod_res', prod_resource['output_value'])

        test_res_output = self.client.stacks.output_show(
            stack_id, 'test_res1_value')['output']
        self.assertEqual('just in test env',
                         test_res_output['output_value'])

        beijing_prod_res = self.client.stacks.output_show(
            stack_id, 'beijing_prod_res')['output']
        self.assertEqual('no_prod_res', beijing_prod_res['output_value'])

    def test_stack_create_update_cfn_template_test_to_prod(self):
        stack_identifier = self.stack_create(template=cfn_template)
        self.res_assert_for_test(stack_identifier)
        self.output_assert_for_test(stack_identifier)

        parms = {'zone': 'fuzhou'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier, fj_zone=True)
        self.output_assert_for_test(stack_identifier)

        parms = {'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier, shannxi_provice=True)
        self.output_assert_for_test(stack_identifier)

        parms = {'env_type': 'prod'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier)
        self.output_assert_for_prod(stack_identifier)

        parms = {'env_type': 'prod',
                 'zone': 'shanghai'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, False)
        self.output_assert_for_prod(stack_identifier, False)

        parms = {'env_type': 'prod',
                 'zone': 'xiamen'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, bj_prod=False, fj_zone=True)
        self.output_assert_for_prod(stack_identifier, False)

        parms = {'env_type': 'prod',
                 'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, bj_prod=False,
                                 fj_zone=False, shannxi_provice=True)
        self.output_assert_for_prod(stack_identifier, False)

    def test_stack_create_update_cfn_template_prod_to_test(self):
        parms = {'env_type': 'prod'}
        stack_identifier = self.stack_create(template=cfn_template,
                                             parameters=parms)
        self.res_assert_for_prod(stack_identifier)
        self.output_assert_for_prod(stack_identifier)

        parms = {'zone': 'xiamen',
                 'env_type': 'prod'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, bj_prod=False, fj_zone=True)
        self.output_assert_for_prod(stack_identifier, bj_prod=False)

        parms = {'zone': 'xianyang',
                 'env_type': 'prod'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, bj_prod=False,
                                 fj_zone=False, shannxi_provice=True)
        self.output_assert_for_prod(stack_identifier, bj_prod=False)

        parms = {'zone': 'shanghai',
                 'env_type': 'prod'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, bj_prod=False,
                                 fj_zone=False, shannxi_provice=False)
        self.output_assert_for_prod(stack_identifier, bj_prod=False)

        parms = {'env_type': 'test'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier)
        self.output_assert_for_test(stack_identifier)

        parms = {'env_type': 'test',
                 'zone': 'fuzhou'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier, fj_zone=True)
        self.output_assert_for_test(stack_identifier)

        parms = {'env_type': 'test',
                 'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=cfn_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier, fj_zone=False,
                                 shannxi_provice=True)
        self.output_assert_for_test(stack_identifier)

    def test_stack_create_update_hot_template_test_to_prod(self):
        stack_identifier = self.stack_create(template=hot_template)
        self.res_assert_for_test(stack_identifier)
        self.output_assert_for_test(stack_identifier)

        parms = {'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier, shannxi_provice=True)
        self.output_assert_for_test(stack_identifier)

        parms = {'env_type': 'prod'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier)
        self.output_assert_for_prod(stack_identifier)

        parms = {'env_type': 'prod',
                 'zone': 'shanghai'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, False)
        self.output_assert_for_prod(stack_identifier, False)

        parms = {'env_type': 'prod',
                 'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, False, shannxi_provice=True)
        self.output_assert_for_prod(stack_identifier, False)

    def test_stack_create_update_hot_template_prod_to_test(self):
        parms = {'env_type': 'prod'}
        stack_identifier = self.stack_create(template=hot_template,
                                             parameters=parms)
        self.res_assert_for_prod(stack_identifier)
        self.output_assert_for_prod(stack_identifier)

        parms = {'env_type': 'prod',
                 'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_prod(stack_identifier, False, shannxi_provice=True)
        self.output_assert_for_prod(stack_identifier, False)

        parms = {'env_type': 'test'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier)
        self.output_assert_for_test(stack_identifier)

        parms = {'env_type': 'test',
                 'zone': 'xianyang'}
        self.update_stack(stack_identifier,
                          template=hot_template,
                          parameters=parms)

        self.res_assert_for_test(stack_identifier, fj_zone=False,
                                 shannxi_provice=True)
        self.output_assert_for_test(stack_identifier)

    def test_condition_rename(self):
        stack_identifier = self.stack_create(template=before_rename_tmpl)
        self.update_stack(stack_identifier, template=after_rename_tmpl)
        self.update_stack(stack_identifier, template=fail_rename_tmpl,
                          expected_status='UPDATE_FAILED')
        self.update_stack(stack_identifier, template=recover_rename_tmpl)


root_output_tmpl = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    type: string
    default: test
conditions:
  cd1: {equals : [{get_param: env_type}, "prod"]}
resources:
  nested:
    type: nested_output.yaml
    properties:
      env_type: {get_param: env_type}
outputs:
  standard:
    value: {get_attr: [nested, standard]}
  cond:
    value: {get_attr: [nested, cond]}
    condition: cd1
  cond_value:
    value: {get_attr: [nested, cond_value]}
'''

nested_output_tmpl = '''
heat_template_version: 2016-10-14
parameters:
  env_type:
    type: string
conditions:
  cd1: {equals : [{get_param: env_type}, "prod"]}
outputs:
  standard:
    value: hello
  cond:
    value: world
    condition: cd1
  cond_value:
    value: {if: [cd1, 'prod', 'test']}
'''


class CreateNestedOutputConditionTest(functional_base.FunctionalTestsBase):

    def test_condition_nested_outputs(self):
        stack_identifier = self.stack_create(template=root_output_tmpl,
                                             files={'nested_output.yaml':
                                                    nested_output_tmpl})

        standard = self.client.stacks.output_show(stack_identifier,
                                                  'standard')['output']
        self.assertEqual('hello', standard['output_value'])

        cond = self.client.stacks.output_show(stack_identifier,
                                              'cond')['output']
        self.assertIsNone(cond['output_value'])

        cond_val = self.client.stacks.output_show(stack_identifier,
                                                  'cond_value')['output']
        self.assertEqual('test', cond_val['output_value'])
