# 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 json
import re

import django
from django.conf import settings
from django.core import exceptions
from django.core.urlresolvers import reverse
from django import http
from django.test.utils import override_settings
from django.utils import html

from mox3.mox import IsA
import six

from heatclient.common import template_format as hc_format
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test

from openstack_dashboard.dashboards.project.stacks import api as project_api
from openstack_dashboard.dashboards.project.stacks import forms
from openstack_dashboard.dashboards.project.stacks import mappings
from openstack_dashboard.dashboards.project.stacks import tables


INDEX_TEMPLATE = 'horizon/common/_data_table_view.html'
INDEX_URL = reverse('horizon:project:stacks:index')
DETAIL_URL = 'horizon:project:stacks:detail'


class MockResource(object):
    def __init__(self, resource_type, physical_resource_id):
        self.resource_type = resource_type
        self.physical_resource_id = physical_resource_id


class MappingsTests(test.TestCase):

    def test_mappings(self):

        def assertMappingUrl(url, resource_type, physical_resource_id):
            mock = MockResource(resource_type, physical_resource_id)
            mock_url = mappings.resource_to_url(mock)
            self.assertEqual(url, mock_url)

        assertMappingUrl(
            '/project/networks/subnets/aaa/detail',
            'OS::Neutron::Subnet',
            'aaa')
        assertMappingUrl(
            None,
            'OS::Neutron::Subnet',
            None)
        assertMappingUrl(
            None,
            None,
            None)
        assertMappingUrl(
            None,
            'AWS::AutoScaling::LaunchConfiguration',
            'aaa')
        assertMappingUrl(
            '/project/instances/aaa/',
            'AWS::EC2::Instance',
            'aaa')
        assertMappingUrl(
            '/project/containers/container/aaa/',
            'OS::Swift::Container',
            'aaa')
        assertMappingUrl(
            None,
            'Foo::Bar::Baz',
            'aaa')
        assertMappingUrl(
            '/project/instances/aaa/',
            'OS::Nova::Server',
            'aaa')
        assertMappingUrl(
            '/project/stacks/stack/aaa/',
            'OS::Heat::ResourceGroup',
            'aaa')

    def test_stack_output(self):
        self.assertEqual(u'<pre>foo</pre>', mappings.stack_output('foo'))
        self.assertEqual(u'', mappings.stack_output(None))

        outputs = ['one', 'two', 'three']
        # On Python 3, the pretty JSON output doesn't add space before newline
        if six.PY3:
            expected_text = """[\n  "one",\n  "two",\n  "three"\n]"""
        else:
            expected_text = """[\n  "one", \n  "two", \n  "three"\n]"""

        self.assertEqual(u'<pre>%s</pre>' % html.escape(expected_text),
                         mappings.stack_output(outputs))

        outputs = {'foo': 'bar'}
        expected_text = """{\n  "foo": "bar"\n}"""
        self.assertEqual(u'<pre>%s</pre>' % html.escape(expected_text),
                         mappings.stack_output(outputs))

        self.assertEqual(
            u'<a href="http://www.example.com/foo" target="_blank">'
            'http://www.example.com/foo</a>',
            mappings.stack_output('http://www.example.com/foo'))


class StackTests(test.TestCase):

    @override_settings(API_RESULT_PAGE_SIZE=2)
    @test.create_stubs({api.heat: ('stacks_list',)})
    def test_index_paginated(self):
        stacks = self.stacks.list()[:5]
        filters = {}
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=None,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks, True, True])
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=None,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks[:2], True, True])
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=stacks[2].id,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks[2:4], True, True])
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=stacks[4].id,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks[4:], True, True])
        self.mox.ReplayAll()

        url = reverse('horizon:project:stacks:index')
        res = self.client.get(url)
        # get all
        self.assertEqual(len(res.context['stacks_table'].data),
                         len(stacks))
        self.assertTemplateUsed(res, INDEX_TEMPLATE)

        res = self.client.get(url)
        # get first page with 2 items
        self.assertEqual(len(res.context['stacks_table'].data),
                         settings.API_RESULT_PAGE_SIZE)

        url = "%s?%s=%s" % (reverse('horizon:project:stacks:index'),
                            tables.StacksTable._meta.pagination_param,
                            stacks[2].id)
        res = self.client.get(url)
        # get second page (items 2-4)
        self.assertEqual(len(res.context['stacks_table'].data),
                         settings.API_RESULT_PAGE_SIZE)

        url = "%s?%s=%s" % (reverse('horizon:project:stacks:index'),
                            tables.StacksTable._meta.pagination_param,
                            stacks[4].id)
        res = self.client.get(url)
        # get third page (item 5)
        self.assertEqual(len(res.context['stacks_table'].data),
                         1)

    @override_settings(API_RESULT_PAGE_SIZE=2)
    @test.create_stubs({api.heat: ('stacks_list',)})
    def test_index_prev_paginated(self):
        stacks = self.stacks.list()[:3]
        filters = {}
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=None,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks, True, False])
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=None,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks[:2], True, True])
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=stacks[2].id,
                             paginate=True,
                             sort_dir='desc',
                             filters=filters) \
            .AndReturn([stacks[2:], True, True])
        api.heat.stacks_list(IsA(http.HttpRequest),
                             marker=stacks[2].id,
                             paginate=True,
                             sort_dir='asc',
                             filters=filters) \
            .AndReturn([stacks[:2], True, True])
        self.mox.ReplayAll()

        url = reverse('horizon:project:stacks:index')
        res = self.client.get(url)
        # get all
        self.assertEqual(len(res.context['stacks_table'].data),
                         len(stacks))
        self.assertTemplateUsed(res, INDEX_TEMPLATE)

        res = self.client.get(url)
        # get first page with 2 items
        self.assertEqual(len(res.context['stacks_table'].data),
                         settings.API_RESULT_PAGE_SIZE)

        url = "%s?%s=%s" % (reverse('horizon:project:stacks:index'),
                            tables.StacksTable._meta.pagination_param,
                            stacks[2].id)
        res = self.client.get(url)
        # get second page (item 3)
        self.assertEqual(len(res.context['stacks_table'].data), 1)

        url = "%s?%s=%s" % (reverse('horizon:project:stacks:index'),
                            tables.StacksTable._meta.prev_pagination_param,
                            stacks[2].id)
        res = self.client.get(url)
        # prev back to get first page with 2 pages
        self.assertEqual(len(res.context['stacks_table'].data),
                         settings.API_RESULT_PAGE_SIZE)

    @test.create_stubs({api.heat: ('stack_create', 'template_validate'),
                        api.neutron: ('network_list_for_tenant', )})
    def test_launch_stack(self):
        template = self.stack_templates.first()
        stack = self.stacks.first()

        api.heat.template_validate(IsA(http.HttpRequest),
                                   files={},
                                   template=hc_format.parse(template.data)) \
           .AndReturn(json.loads(template.validate))

        api.heat.stack_create(IsA(http.HttpRequest),
                              stack_name=stack.stack_name,
                              timeout_mins=60,
                              disable_rollback=True,
                              template=None,
                              parameters=IsA(dict),
                              password='password',
                              files=None)
        api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
                                            self.tenant.id) \
            .AndReturn(self.networks.list())
        api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
                                            self.tenant.id) \
            .AndReturn(self.networks.list())

        self.mox.ReplayAll()

        url = reverse('horizon:project:stacks:select_template')
        res = self.client.get(url)
        self.assertTemplateUsed(res, 'project/stacks/select_template.html')

        form_data = {'template_source': 'raw',
                     'template_data': template.data,
                     'method': forms.TemplateForm.__name__}
        res = self.client.post(url, form_data)
        self.assertTemplateUsed(res, 'project/stacks/create.html')

        url = reverse('horizon:project:stacks:launch')
        form_data = {'template_source': 'raw',
                     'template_data': template.data,
                     'password': 'password',
                     'parameters': template.validate,
                     'stack_name': stack.stack_name,
                     "timeout_mins": 60,
                     "disable_rollback": True,
                     "__param_DBUsername": "admin",
                     "__param_LinuxDistribution": "F17",
                     "__param_InstanceType": "m1.small",
                     "__param_KeyName": "test",
                     "__param_DBPassword": "admin",
                     "__param_DBRootPassword": "admin",
                     "__param_DBName": "wordpress",
                     "__param_Network": self.networks.list()[0]['id'],
                     'method': forms.CreateStackForm.__name__}
        res = self.client.post(url, form_data)
        self.assertRedirectsNoFollow(res, INDEX_URL)

    @test.create_stubs({api.heat: ('stack_create', 'template_validate'),
                        api.neutron: ('network_list_for_tenant', )})
    def test_launch_stack_with_environment(self):
        template = self.stack_templates.first()
        environment = self.stack_environments.first()
        stack = self.stacks.first()

        api.heat.template_validate(IsA(http.HttpRequest),
                                   files={},
                                   template=hc_format.parse(template.data),
                                   environment=environment.data) \
           .AndReturn(json.loads(template.validate))

        api.heat.stack_create(IsA(http.HttpRequest),
                              stack_name=stack.stack_name,
                              timeout_mins=60,
                              disable_rollback=True,
                              template=None,
                              environment=environment.data,
                              parameters=IsA(dict),
                              password='password',
                              files=None)
        api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
                                            self.tenant.id) \
            .AndReturn(self.networks.list())
        api.neutron.network_list_for_tenant(IsA(http.HttpRequest),
                                            self.tenant.id) \
            .AndReturn(self.networks.list())

        self.mox.ReplayAll()

        url = reverse('horizon:project:stacks:select_template')
        res = self.client.get(url)
        self.assertTemplateUsed(res, 'project/stacks/select_template.html')

        form_data = {'template_source': 'raw',
                     'template_data': template.data,
                     'environment_source': 'raw',
                     'environment_data': environment.data,
                     'method': forms.TemplateForm.__name__}
        res = self.client.post(url, form_data)
        self.assertTemplateUsed(res, 'project/stacks/create.html')

        url = reverse('horizon:project:stacks:launch')
        form_data = {'template_source': 'raw',
                     'template_data': template.data,
                     'environment_source': 'raw',
                     'environment_data': environment.data,
                     'password': 'password',
                     'parameters': template.validate,
                     'stack_name': stack.stack_name,
                     "timeout_mins": 60,
                     "disable_rollback": True,
                     "__param_DBUsername": "admin",
                     "__param_LinuxDistribution": "F17",
                     "__param_InstanceType": "m1.small",
                     "__param_KeyName": "test",
                     "__param_DBPassword": "admin",
                     "__param_DBRootPassword": "admin",
                     "__param_DBName": "wordpress",
                     "__param_Network": self.networks.list()[0]['id'],
                     'method': forms.CreateStackForm.__name__}
        res = self.client.post(url, form_data)
        self.assertRedirectsNoFollow(res, INDEX_URL)

    @test.create_stubs({api.heat: ('template_validate',)})
    def test_launch_stack_with_hidden_parameters(self):
        template = {
            'data': ('heat_template_version: 2013-05-23\n'
                     'parameters:\n'
                     '  public_string:\n'
                     '    type: string\n'
                     '  secret_string:\n'
                     '    type: string\n'
                     '    hidden: true\n'),
            'validate': {
                'Description': 'No description',
                'Parameters': {
                    'public_string': {
                        'Label': 'public_string',
                        'Description': '',
                        'Type': 'String',
                        'NoEcho': 'false'
                    },
                    'secret_string': {
                        'Label': 'secret_string',
                        'Description': '',
                        'Type': 'String',
                        'NoEcho': 'true'
                    }
                }
            }
        }
        api.heat.template_validate(IsA(http.HttpRequest),
                                   files={},
                                   template=hc_format.parse(template['data'])) \
           .AndReturn(template['validate'])

        self.mox.ReplayAll()

        url = reverse('horizon:project:stacks:select_template')
        res = self.client.get(url)
        self.assertTemplateUsed(res, 'project/stacks/select_template.html')

        form_data = {'template_source': 'raw',
                     'template_data': template['data'],
                     'method': forms.TemplateForm.__name__}
        res = self.client.post(url, form_data)
        self.assertTemplateUsed(res, 'project/stacks/create.html')

        # ensure the fields were rendered correctly
        if django.VERSION >= (1, 10):
            pattern = ('<input class="form-control" '
                       'id="id___param_public_string" '
                       'name="__param_public_string" type="text" required/>')
            secret = ('<input class="form-control" '
                      'id="id___param_secret_string" '
                      'name="__param_secret_string" '
                      'type="password" required>')
        else:
            pattern = ('<input class="form-control" '
                       'id="id___param_public_string" '
                       'name="__param_public_string" type="text" />')
            secret = ('<input class="form-control" '
                      'id="id___param_secret_string" '
                      'name="__param_secret_string" '
                      'type="password" />')

        self.assertContains(res, pattern, html=True)
        self.assertContains(res, secret, html=True)

    @test.create_stubs({api.heat: ('template_validate',)})
    def test_launch_stack_with_parameter_group(self):
        template = {
            'data': ('heat_template_version: 2013-05-23\n'
                     'parameters:\n'
                     '  last_param:\n'
                     '    type: string\n'
                     '  first_param:\n'
                     '    type: string\n'
                     '  middle_param:\n'