##############################################################################
# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, 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, AbstractSelectOrCreate
from resource_inventory.models import ConfigBundle, OPNFV_SETTINGS
from workflow.forms import OPNFVSelectionForm, OPNFVNetworkRoleForm, OPNFVHostRoleForm, SWConfigSelectorForm, BasicMetaForm


class OPNFV_Resource_Select(AbstractSelectOrCreate):
    title = "Select Software Configuration"
    description = "Choose the software bundle you wish to use as a base for your OPNFV configuration"
    short_title = "software config"
    form = SWConfigSelectorForm

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.select_repo_key = self.repo.SELECTED_CONFIG_BUNDLE

    def get_form_queryset(self):
        user = self.repo_get(self.repo.SESSION_USER)
        qs = ConfigBundle.objects.filter(owner=user)
        return qs

    def put_confirm_info(self, bundle):
        confirm_dict = self.repo_get(self.repo.CONFIRMATION)
        confirm_dict['software bundle'] = bundle.name
        confirm_dict['hardware POD'] = bundle.bundle.name
        self.repo_put(self.repo.CONFIRMATION, confirm_dict)

    def get_page_context(self):
        return {
            'select_type': 'swconfig',
            'select_type_title': 'Software Config',
            'addable_type_num': 2
        }


class Pick_Installer(WorkflowStep):
    template = 'config_bundle/steps/pick_installer.html'
    title = 'Pick OPNFV Installer'
    description = 'Choose which OPNFV installer to use'
    short_title = "opnfv installer"
    modified_key = "installer_step"

    def update_confirmation(self):
        confirm = self.repo_get(self.repo.CONFIRMATION, {})
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        installer = models.get("installer_chosen")
        scenario = models.get("scenario_chosen")
        if not (installer and scenario):
            return
        confirm['installer'] = installer.name
        confirm['scenario'] = scenario.name
        self.repo_put(self.repo.CONFIRMATION, confirm)

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

        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        initial = {
            "installer": models.get("installer_chosen"),
            "scenario": models.get("scenario_chosen")
        }

        context["form"] = OPNFVSelectionForm(initial=initial)
        return context

    def post(self, post_data, user):
        form = OPNFVSelectionForm(post_data)
        if form.is_valid():
            installer = form.cleaned_data['installer']
            scenario = form.cleaned_data['scenario']
            models = self.repo_get(self.repo.OPNFV_MODELS, {})
            models['installer_chosen'] = installer
            models['scenario_chosen'] = scenario
            self.repo_put(self.repo.OPNFV_MODELS, models)
            self.update_confirmation()
            self.set_valid("Step Completed")
        else:
            self.set_invalid("Please select an Installer and Scenario")


class Assign_Network_Roles(WorkflowStep):
    template = 'config_bundle/steps/assign_network_roles.html'
    title = 'Pick Network Roles'
    description = 'Choose what role each network should get'
    short_title = "network roles"
    modified_key = "net_roles_step"

    """
    to do initial filling, repo should have a "network_roles" array with the following structure for each element:
    {
        "role": <NetworkRole object ref>,
        "network": <Network object ref>
    }
    """
    def create_netformset(self, roles, config_bundle, data=None):
        roles_initial = []
        set_roles = self.repo_get(self.repo.OPNFV_MODELS, {}).get("network_roles")
        if set_roles:
            roles_initial = set_roles
        else:
            for role in OPNFV_SETTINGS.NETWORK_ROLES:
                roles_initial.append({"role": role})

        Formset = formset_factory(OPNFVNetworkRoleForm, extra=0)
        kwargs = {
            "initial": roles_initial,
            "form_kwargs": {"config_bundle": config_bundle}
        }
        formset = None
        if data:
            formset = Formset(data, **kwargs)
        else:
            formset = Formset(**kwargs)
        return formset

    def get_context(self):
        context = super(Assign_Network_Roles, self).get_context()
        config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
        if config_bundle is None:
            context["unavailable"] = True
            return context

        roles = OPNFV_SETTINGS.NETWORK_ROLES
        formset = self.create_netformset(roles, config_bundle)
        context['formset'] = formset

        return context

    def update_confirmation(self):
        confirm = self.repo_get(self.repo.CONFIRMATION, {})
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        roles = models.get("network_roles")
        if not roles:
            return
        confirm['network roles'] = {}
        for role in roles:
            confirm['network roles'][role['role']] = role['network'].name
        self.repo_put(self.repo.CONFIRMATION, confirm)

    def post(self, post_data, user):
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
        roles = OPNFV_SETTINGS.NETWORK_ROLES
        net_role_formset = self.create_netformset(roles, config_bundle, data=post_data)
        if net_role_formset.is_valid():
            results = []
            for form in net_role_formset:
                results.append({
                    "role": form.cleaned_data['role'],
                    "network": form.cleaned_data['network']
                })
            models['network_roles'] = results
            self.set_valid("Completed")
            self.repo_put(self.repo.OPNFV_MODELS, models)
            self.update_confirmation()
        else:
            self.set_invalid("Please complete all fields")


class Assign_Host_Roles(WorkflowStep):  # taken verbatim from Define_Software in sw workflow, merge the two?
    template = 'config_bundle/steps/assign_host_roles.html'
    title = 'Pick Host Roles'
    description = "Choose the role each machine will have in your OPNFV pod"
    short_title = "host roles"
    modified_key = "host_roles_step"

    def create_host_role_formset(self, hostlist=[], data=None):
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        host_roles = models.get("host_roles", [])
        if not host_roles:
            for host in hostlist:
                initial = {"host_name": host.resource.name}
                host_roles.append(initial)
        models['host_roles'] = host_roles
        self.repo_put(self.repo.OPNFV_MODELS, models)

        HostFormset = formset_factory(OPNFVHostRoleForm, extra=0)

        kwargs = {"initial": host_roles}
        formset = None
        if data:
            formset = HostFormset(data, **kwargs)
        else:
            formset = HostFormset(**kwargs)

        return formset

    def get_context(self):
        context = super(Assign_Host_Roles, self).get_context()
        config = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
        if config is None:
            context['error'] = "Please select a Configuration on the first step"

        formset = self.create_host_role_formset(hostlist=config.bundle.getHosts())
        context['formset'] = formset

        return context

    def get_host_role_mapping(self, host_roles, hostname):
        for obj in host_roles:
            if hostname == obj['host_name']:
                return obj
        return None

    def update_confirmation(self):
        confirm = self.repo_get(self.repo.CONFIRMATION, {})
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        roles = models.get("host_roles")
        if not roles:
            return
        confirm['host roles'] = {}
        for role in roles:
            confirm['host roles'][role['host_name']] = role['role'].name
        self.repo_put(self.repo.CONFIRMATION, confirm)

    def post(self, post_data, user):
        formset = self.create_host_role_formset(data=post_data)

        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        host_roles = models.get("host_roles", [])

        has_jumphost = False
        if formset.is_valid():
            for form in formset:
                hostname = form.cleaned_data['host_name']
                role = form.cleaned_data['role']
                mapping = self.get_host_role_mapping(host_roles, hostname)
                mapping['role'] = role
                if "jumphost" in role.name.lower():
                    has_jumphost = True

            models['host_roles'] = host_roles
            self.repo_put(self.repo.OPNFV_MODELS, models)
            self.update_confirmation()

            if not has_jumphost:
                self.set_invalid('Must have at least one "Jumphost" per POD')
            else:
                self.set_valid("Completed")
        else:
            self.set_invalid("Please complete all fields")


class MetaInfo(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(MetaInfo, self).get_context()

        initial = self.repo_get(self.repo.OPNFV_MODELS, {}).get("meta", {})
        context["form"] = BasicMetaForm(initial=initial)
        return context

    def update_confirmation(self):
        confirm = self.repo_get(self.repo.CONFIRMATION, {})
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        meta = models.get("meta")
        if not meta:
            return
        confirm['name'] = meta['name']
        confirm['description'] = meta['description']
        self.repo_put(self.repo.CONFIRMATION, confirm)

    def post(self, post_data, user):
        models = self.repo_get(self.repo.OPNFV_MODELS, {})
        info = models.get("meta", {})

        form = BasicMetaForm(post_data)
        if form.is_valid():
            info['name'] = form.cleaned_data['name']
            info['description'] = form.cleaned_data['description']
            models['meta'] = info
            self.repo_put(self.repo.OPNFV_MODELS, models)
            self.update_confirmation()
            self.set_valid("Complete")
        else:
            self.set_invalid("Please correct the errors shown below")
        self.repo_put(self.repo.OPNFV_MODELS, models)