# -*- coding: utf8 -*-
#
#    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 logging
import urlparse

from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django import http
from django import shortcuts
import django.utils.text
from django.utils.translation import ugettext_lazy as _
import heatclient
import horizon.forms
from horizon import messages

from tuskar_ui import api
from tuskar_ui.infrastructure.overview import forms
from tuskar_ui.infrastructure import views


INDEX_URL = 'horizon:infrastructure:overview:index'

LOG = logging.getLogger(__name__)


def _steps_message(messages):
    total_steps = len(messages)
    completed_steps = len([m for m in messages if not m.get('is_critical')])
    return _("{0} of {1} Steps Completed").format(completed_steps, total_steps)


def _get_role_data(plan, stack, form, role):
    """Gathers data about a single deployment role.

    Gathers data about a single deployment role from the related Overcloud
    and Role objects, and presents it in the form convenient for use
    from the template.

    """
    data = {
        'id': role.id,
        'role': role,
        'name': role.name,
        'planned_node_count': plan.get_role_node_count(role),
        'field': form['%s-count' % role.id] if form else '',
    }

    if stack:
        resources = stack.resources(role=role, with_joins=True)
        nodes = [r.node for r in resources]
        node_count = len(nodes)

        deployed_node_count = 0
        deploying_node_count = 0
        error_node_count = 0
        waiting_node_count = node_count

        status = 'warning'
        if nodes:
            deployed_node_count = sum(1 for node in nodes
                                      if node.instance.status == 'ACTIVE')
            deploying_node_count = sum(1 for node in nodes
                                       if node.instance.status == 'BUILD')
            error_node_count = sum(1 for node in nodes
                                   if node.instance.status == 'ERROR')
            waiting_node_count = (node_count - deployed_node_count -
                                  deploying_node_count - error_node_count)

            if error_node_count or 'FAILED' in stack.stack_status:
                status = 'danger'
            elif deployed_node_count == data['planned_node_count']:
                status = 'success'
            else:
                status = 'info'

        finished = deployed_node_count == data['planned_node_count']
        if finished:
            icon = 'fa-check'
        elif status in ('danger', 'warning'):
            icon = 'fa-exclamation'
        else:
            icon = 'fa-spinner fa-spin'

        data.update({
            'status': status,
            'finished': finished,
            'total_node_count': node_count,
            'deployed_node_count': deployed_node_count,
            'deploying_node_count': deploying_node_count,
            'waiting_node_count': waiting_node_count,
            'error_node_count': error_node_count,
            'icon': icon,
        })

    # TODO(rdopieralski) get this from ceilometer
    # data['capacity'] = 20
    return data


class IndexView(horizon.forms.ModalFormView, views.StackMixin):
    template_name = 'infrastructure/overview/index.html'
    form_class = forms.EditPlan
    success_url = reverse_lazy(INDEX_URL)

    def get_progress_update(self, request, data):
        return {
            'progress': data.get('progress'),
            'show_last_events': data.get('show_last_events'),
            'last_events_title': unicode(data.get('last_events_title')),
            'last_events': [{
                'event_time': event.event_time,
                'resource_name': event.resource_name,
                'resource_status': event.resource_status,
                'resource_status_reason': event.resource_status_reason,
            } for event in data.get('last_events', [])],
            'roles': [{
                'status': role.get('status', 'warning'),
                'finished': role.get('finished', False),
                'name': role.get('name', ''),
                'slug': django.utils.text.slugify(role.get('name', '')),
                'id': role.get('id', ''),
                'total_node_count': role.get('node_count', 0),
                'deployed_node_count': role.get('deployed_node_count', 0),
                'deploying_node_count': role.get('deploying_node_count', 0),
                'waiting_node_count': role.get('waiting_node_count', 0),
                'error_node_count': role.get('error_node_count', 0),
                'planned_node_count': role.get('planned_node_count', 0),
                'icon': role.get('icon', ''),
            } for role in data.get('roles', [])],
        }

    def get(self, request, *args, **kwargs):
        if request.META.get('HTTP_X_HORIZON_PROGRESS', ''):
            # If it's an AJAX call for progress update, send it.
            data = self.get_data(request, {})
            return http.HttpResponse(
                json.dumps(self.get_progress_update(request, data)),
                content_type='application/json',
            )
        return super(IndexView, self).get(request, *args, **kwargs)

    def get_form(self, form_class):
        return form_class(self.request, **self.get_form_kwargs())

    def get_context_data(self, *args, **kwargs):
        context = super(IndexView, self).get_context_data(*args, **kwargs)
        context.update(self.get_data(self.request, context))
        return context

    def get_data(self, request, context, *args, **kwargs):
        plan = api.tuskar.Plan.get_the_plan(request)
        stack = self.get_stack()
        form = context.get('form')

        context['plan'] = plan
        context['stack'] = stack

        roles = [_get_role_data(plan, stack, form, role)
                 for role in plan.role_list]
        context['roles'] = roles

        if stack:
            context['show_last_events'] = True
            failed_events = [e for e in stack.events
                             if 'FAILED' in e.resource_status and
                             'aborted' not in e.resource_status_reason][-3:]

            if failed_events:
                context['last_events_title'] = _('Last failed events')
                context['last_events'] = failed_events
            else:
                context['last_events_title'] = _('Last event')
                context['last_events'] = [stack.events[0]]

            if stack.is_deleting or stack.is_delete_failed:
                # TODO(lsmola) since at this point we don't have total number
                # of nodes we will hack this around, till API can show this
                # information. So it will actually show progress like the total
                # number is 10, or it will show progress of 5%. Ugly, but
                # workable.
                total_num_nodes_count = 10

                try:
                    resources_count = len(
                        stack.resources(with_joins=False))
                except heatclient.exc.HTTPNotFound:
                    # Immediately after undeploying has started, heat returns
                    # this exception so we can take it as kind of init of
                    # undeploying.
                    resources_count = total_num_nodes_count

                # TODO(lsmola) same as hack above
                total_num_nodes_count = max(
                    resources_count, total_num_nodes_count)

                context['progress'] = min(95, max(
                    5, 100 * float(resources_count) / total_num_nodes_count))
            elif stack.is_deploying or stack.is_updating:
                total = sum(d['total_node_count'] for d in roles)
                context['progress'] = min(95, max(
                    5, 100 * sum(float(d.get('deployed_node_count', 0))
                                 for d in roles) / (total or 1)
                ))
            else:
                # stack is active
                if not stack.is_failed:
                    context['show_last_events'] = False
                context['progress'] = 100
                controller_role = plan.get_role_by_name("Controller")
                context['admin_password'] = plan.parameter_value(
                    controller_role.parameter_prefix + 'AdminPassword')

                context['dashboard_urls'] = stack.dashboard_urls
                no_proxy = [urlparse.urlparse(url).hostname
                            for url in stack.dashboard_urls]
                context['no_proxy'] = ",".join(no_proxy)
                context['auth_url'] = stack.keystone_auth_url
        else:
            messages = forms.validate_plan(request, plan)
            context['plan_messages'] = messages
            context['plan_invalid'] = any(message.get('is_critical')
                                          for message in messages)
            context['steps_message'] = _steps_message(messages)
        return context

    def post(self, request, *args, **kwargs):
        """If the post comes from ajax, return validation results as json."""

        if not request.META.get('HTTP_X_HORIZON_VALIDATE', ''):
            return super(IndexView, self).post(request, *args, **kwargs)
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        if form.is_valid():
            handled = form.handle(self.request, form.cleaned_data)
        else:
            handled = False
        if handled:
            messages = forms.validate_plan(request, form.plan)
        else:
            messages = [{
                'text': _(u"Error saving the plan."),
                'is_critical': True,
            }]
            messages.extend({
                'text': repr(error),
            } for error in form.non_field_errors)
            messages.extend({
                'text': repr(error),
            } for field in form.fields for error in field.errors)
        # We need to unlazify all the lazy urls and translations.
        return http.HttpResponse(json.dumps({
            'plan_invalid': any(m.get('is_critical') for m in messages),
            'steps_message': _steps_message(messages),
            'messages': [{
                'text': unicode(m.get('text', '')),
                'is_critical': m.get('is_critical', False),
                'indent': m.get('indent', 0),
                'classes': m['classes'],
            } for m in messages],
        }), content_type='application/json')


class DeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
    form_class = forms.DeployOvercloud
    template_name = 'infrastructure/overview/deploy_confirmation.html'
    submit_label = _("Deploy")

    def get_context_data(self, **kwargs):
        context = super(DeployConfirmationView,
                        self).get_context_data(**kwargs)
        plan = api.tuskar.Plan.get_the_plan(self.request)

        context['autogenerated_parameters'] = (
            plan.list_generated_parameters(with_prefix=False).keys())
        return context

    def get_success_url(self):
        return reverse(INDEX_URL)


class UndeployConfirmationView(horizon.forms.ModalFormView, views.StackMixin):
    form_class = forms.UndeployOvercloud
    template_name = 'infrastructure/overview/undeploy_confirmation.html'
    submit_label = _("Undeploy")

    def get_success_url(self):
        return reverse(INDEX_URL)

    def get_context_data(self, **kwargs):
        context = super(UndeployConfirmationView,
                        self).get_context_data(**kwargs)
        context['stack_id'] = self.get_stack().id
        return context

    def get_initial(self, **kwargs):
        initial = super(UndeployConfirmationView, self).get_initial(**kwargs)
        initial['stack_id'] = self.get_stack().id
        return initial


class PostDeployInitView(horizon.forms.ModalFormView, views.StackMixin):
    form_class = forms.PostDeployInit
    template_name = 'infrastructure/overview/post_deploy_init.html'
    submit_label = _("Initialize")

    def get_success_url(self):
        return reverse(INDEX_URL)

    def get_context_data(self, **kwargs):
        context = super(PostDeployInitView,
                        self).get_context_data(**kwargs)
        context['stack_id'] = self.get_stack().id
        return context

    def get_initial(self, **kwargs):
        initial = super(PostDeployInitView, self).get_initial(**kwargs)
        initial['stack_id'] = self.get_stack().id
        initial['admin_email'] = getattr(self.request.user, 'email', '')
        return initial


class ScaleOutView(horizon.forms.ModalFormView, views.StackMixin):
    form_class = forms.ScaleOut
    template_name = "infrastructure/overview/scale_out.html"
    submit_label = _("Deploy Changes")

    def get_success_url(self):
        return reverse(INDEX_URL)

    def get_form(self, form_class):
        return form_class(self.request, **self.get_form_kwargs())

    def get_context_data(self, *args, **kwargs):
        context = super(ScaleOutView, self).get_context_data(*args, **kwargs)
        plan = api.tuskar.Plan.get_the_plan(self.request)
        form = context.get('form')
        roles = [_get_role_data(plan, None, form, role)
                 for role in plan.role_list]
        context.update({
            'roles': roles,
            'plan': plan,
        })
        return context


def _get_openrc_credentials(request):
    plan = api.tuskar.Plan.get_the_plan(request)
    stack = api.heat.Stack.get_by_plan(request, plan)
    no_proxy = [urlparse.urlparse(url).hostname
                for url in stack.dashboard_urls]
    controller_role = plan.get_role_by_name("Controller")
    credentials = dict(tenant_name='admin',
                       auth_url=stack.keystone_auth_url,
                       admin_password=plan.parameter_value(
                           controller_role.parameter_prefix + 'AdminPassword'),
                       no_proxy=",".join(no_proxy))
    return credentials


def download_overcloudrc_file(request):
    template = 'infrastructure/overview/overcloudrc.sh.template'
    try:
        context = _get_openrc_credentials(request)

        response = shortcuts.render(request,
                                    template,
                                    context,
                                    content_type="text/plain")
        response['Content-Disposition'] = ('attachment; '
                                           'filename="overcloudrc"')
        response['Content-Length'] = str(len(response.content))
        return response

    except Exception as e:
        LOG.exception("Exception in DownloadOvercloudrcForm.")
        messages.error(request, _('Error Downloading RC File: %s') % e)
        return shortcuts.redirect(request.build_absolute_uri())
