##############################################################################
# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################


from django.forms import formset_factory

from workflow.models import WorkflowStep
from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm
from workflow.booking_workflow import Abstract_Resource_Select
from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConfiguration


class SWConf_Resource_Select(Abstract_Resource_Select):
    workflow_type = "configuration"


class Define_Software(WorkflowStep):
    template = 'config_bundle/steps/define_software.html'
    title = "Pick Software"
    description = "Choose the opnfv and image of your machines"
    short_title = "host config"

    def build_filter_data(self, hosts_data):
        """
        returns a 2D array of images to exclude
        based on the ordering of the passed
        hosts_data
        """
        filter_data = []
        user = self.repo_get(self.repo.SESSION_USER)
        lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab
        for i, host_data in enumerate(hosts_data):
            host = GenericHost.objects.get(pk=host_data['host_id'])
            wrong_owner = Image.objects.exclude(owner=user).exclude(public=True)
            wrong_host = Image.objects.exclude(host_type=host.profile)
            wrong_lab = Image.objects.exclude(from_lab=lab)
            excluded_images = wrong_owner | wrong_host | wrong_lab
            filter_data.append([])
            for image in excluded_images:
                filter_data[i].append(image.pk)
        return filter_data

    def create_hostformset(self, hostlist, data=None):
        hosts_initial = []
        host_configs = self.repo_get(self.repo.CONFIG_MODELS, {}).get("host_configs", False)
        if host_configs:
            for config in host_configs:
                hosts_initial.append({
                    'host_id': config.host.id,
                    'host_name': config.host.resource.name,
                    'headnode': config.is_head_node,
                    'image': config.image
                })
        else:
            for host in hostlist:
                hosts_initial.append({
                    'host_id': host.id,
                    'host_name': host.resource.name
                })

        HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
        filter_data = self.build_filter_data(hosts_initial)

        class SpecialHostFormset(HostFormset):
            def get_form_kwargs(self, index):
                kwargs = super(SpecialHostFormset, self).get_form_kwargs(index)
                if index is not None:
                    kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index])
                return kwargs

        if data:
            return SpecialHostFormset(data, initial=hosts_initial)
        return SpecialHostFormset(initial=hosts_initial)

    def get_host_list(self, grb=None):
        if grb is None:
            grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
            if not grb:
                return []
        if grb.id:
            return GenericHost.objects.filter(resource__bundle=grb)
        generic_hosts = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("hosts", [])
        return generic_hosts

    def get_context(self):
        context = super(Define_Software, self).get_context()

        grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)

        if grb:
            context["grb"] = grb
            formset = self.create_hostformset(self.get_host_list(grb))
            context["formset"] = formset
            context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1)
        else:
            context["error"] = "Please select a resource first"
            self.set_invalid("Step requires information that is not yet provided by previous step")

        return context

    def post(self, post_data, user):
        models = self.repo_get(self.repo.CONFIG_MODELS, {})
        if "bundle" not in models:
            models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))

        confirm = self.repo_get(self.repo.CONFIRMATION, {})

        hosts = self.get_host_list()
        models['headnode_index'] = post_data.get("headnode", 1)
        formset = self.create_hostformset(hosts, data=post_data)
        has_headnode = False
        if formset.is_valid():
            models['host_configs'] = []
            confirm_hosts = []
            for i, form in enumerate(formset):
                host = hosts[i]
                image = form.cleaned_data['image']
                headnode = form.cleaned_data['headnode']
                if headnode:
                    has_headnode = True
                bundle = models['bundle']
                hostConfig = HostConfiguration(
                    host=host,
                    image=image,
                    bundle=bundle,
                    is_head_node=headnode
                )
                models['host_configs'].append(hostConfig)
                confirm_hosts.append({
                    "name": host.resource.name,
                    "image": image.name,
                    "headnode": headnode
                })

            if not has_headnode:
                self.set_invalid('Must have one "Headnode" per POD')
                return

            self.repo_put(self.repo.CONFIG_MODELS, models)
            if "configuration" not in confirm:
                confirm['configuration'] = {}
            confirm['configuration']['hosts'] = confirm_hosts
            self.repo_put(self.repo.CONFIRMATION, confirm)
            self.set_valid("Completed")
        else:
            self.set_invalid("Please complete all fields")


class Config_Software(WorkflowStep):
    template = 'config_bundle/steps/config_software.html'
    title = "Other Info"
    description = "Give your software config a name, description, and other stuff"
    short_title = "config info"

    def get_context(self):
        context = super(Config_Software, self).get_context()

        initial = {}
        models = self.repo_get(self.repo.CONFIG_MODELS, {})
        bundle = models.get("bundle", False)
        if bundle:
            initial['name'] = bundle.name
            initial['description'] = bundle.description
        context["form"] = BasicMetaForm(initial=initial)
        return context

    def post(self, post_data, user):
        models = self.repo_get(self.repo.CONFIG_MODELS, {})
        if "bundle" not in models:
            models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))

        confirm = self.repo_get(self.repo.CONFIRMATION, {})
        if "configuration" not in confirm:
            confirm['configuration'] = {}

        form = BasicMetaForm(post_data)
        if form.is_valid():
            models['bundle'].name = form.cleaned_data['name']
            models['bundle'].description = form.cleaned_data['description']

            confirm['configuration']['name'] = form.cleaned_data['name']
            confirm['configuration']['description'] = form.cleaned_data['description']
            self.set_valid("Complete")
        else:
            self.set_invalid("Please correct the errors shown below")

        self.repo_put(self.repo.CONFIG_MODELS, models)
        self.repo_put(self.repo.CONFIRMATION, confirm)