##############################################################################
# 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.http import JsonResponse
from django.http.request import QueryDict
from django.urls import reverse

from booking.models import Booking
from workflow.workflow_factory import WorkflowFactory
from workflow.models import Repository
from resource_inventory.models import (
    ResourceTemplate,
    ResourceConfiguration,
    OPNFVConfig
)
from workflow.forms import ManagerForm

import logging
logger = logging.getLogger(__name__)


class SessionManager():
    def active_workflow(self):
        return self.workflows[-1]

    def __init__(self, request=None):
        self.workflows = []
        self.owner = request.user
        self.factory = WorkflowFactory()
        self.result = None

    def set_step_statuses(self, superclass_type, desired_enabled=True):
        workflow = self.active_workflow()
        steps = workflow.steps
        for step in steps:
            if isinstance(step, superclass_type):
                if desired_enabled:
                    step.enable()
                else:
                    step.disable()

    def add_workflow(self, workflow_type=None, **kwargs):
        repo = Repository()
        if(len(self.workflows) >= 1):
            defaults = self.workflows[-1].repository.get_child_defaults()
            repo.set_defaults(defaults)
            repo.el[repo.HAS_RESULT] = False
        repo.el[repo.SESSION_USER] = self.owner
        repo.el[repo.SESSION_MANAGER] = self
        self.workflows.append(
            self.factory.create_workflow(
                workflow_type=workflow_type,
                repo=repo
            )
        )

    def get_redirect(self):
        if isinstance(self.result, Booking):
            return reverse('booking:booking_detail', kwargs={'booking_id': self.result.id})
        return "/"

    def pop_workflow(self, discard=False):
        multiple_wfs = len(self.workflows) > 1
        if multiple_wfs:
            if self.workflows[-1].repository.el[Repository.RESULT]:  # move result
                key = self.workflows[-1].repository.el[Repository.RESULT_KEY]
                result = self.workflows[-1].repository.el[Repository.RESULT]
                self.workflows[-2].repository.el[key] = result
        prev_workflow = self.workflows.pop()
        if self.workflows:
            current_repo = self.workflows[-1].repository
        else:
            current_repo = prev_workflow.repository
        self.result = current_repo.el[current_repo.RESULT]
        if discard:
            current_repo.cancel()
        return multiple_wfs, self.result

    def status(self, request):
        return {
            "steps": [step.to_json() for step in self.active_workflow().steps],
            "active": self.active_workflow().repository.el['active_step'],
            "workflow_count": len(self.workflows)
        }

    def handle_post(self, request):
        form = ManagerForm(request.POST)
        if form.is_valid():
            self.get_active_step().post(
                QueryDict(form.cleaned_data['step_form']),
                user=request.user
            )
            # change step
            if form.cleaned_data['step'] == 'prev':
                self.go_prev()
            if form.cleaned_data['step'] == 'next':
                self.go_next()
        else:
            pass  # Exception?

    def handle_request(self, request):
        if request.method == 'POST':
            self.handle_post(request)
        return self.render(request)

    def render(self, request, **kwargs):
        if self.workflows:
            return JsonResponse({
                "meta": self.status(request),
                "content": self.get_active_step().render_to_string(request),
            })
        else:
            return JsonResponse({
                "redirect": self.get_redirect()
            })

    def post_render(self, request):
        return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)

    def get_active_step(self):
        return self.active_workflow().steps[self.active_workflow().active_index]

    def go_next(self, **kwargs):
        # need to verify current step is valid to allow this
        if self.get_active_step().valid < 200:
            return
        next_step = self.active_workflow().active_index + 1
        if next_step >= len(self.active_workflow().steps):
            raise Exception("Out of bounds request for step")
        while not self.active_workflow().steps[next_step].enabled:
            next_step += 1
        self.active_workflow().repository.el['active_step'] = next_step
        self.active_workflow().active_index = next_step

    def go_prev(self, **kwargs):
        prev_step = self.active_workflow().active_index - 1
        if prev_step < 0:
            raise Exception("Out of bounds request for step")
        while not self.active_workflow().steps[prev_step].enabled:
            prev_step -= 1
        self.active_workflow().repository.el['active_step'] = prev_step
        self.active_workflow().active_index = prev_step

    def prefill_repo(self, target_id, workflow_type):
        self.repository.el[self.repository.EDIT] = True
        edit_object = None
        if workflow_type == 0:
            edit_object = Booking.objects.get(pk=target_id)
            self.prefill_booking(edit_object)
        elif workflow_type == 1:
            edit_object = ResourceTemplate.objects.get(pk=target_id)
            self.prefill_resource(edit_object)
        elif workflow_type == 2:
            edit_object = ResourceTemplate.objects.get(pk=target_id)
            self.prefill_config(edit_object)

    def prefill_booking(self, booking):
        models = self.make_booking_models(booking)
        confirmation = self.make_booking_confirm(booking)
        self.active_workflow().repository.el[self.active_workflow().repository.BOOKING_MODELS] = models
        self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirmation
        self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = self.make_grb_models(booking.resource.template)
        self.active_workflow().repository.el[self.active_workflow().repository.SELECTED_RESOURCE_TEMPLATE] = self.make_grb_models(booking.resource.template)['bundle']
        self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle)

    def prefill_resource(self, resource):
        models = self.make_grb_models(resource)
        confirm = self.make_grb_confirm(resource)
        self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = models
        self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm

    def prefill_config(self, config):
        models = self.make_config_models(config)
        confirm = self.make_config_confirm(config)
        self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = models
        self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
        grb_models = self.make_grb_models(config.bundle)
        self.active_workflow().repository.el[self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS] = grb_models

    def make_grb_models(self, resource):
        models = self.active_workflow().repository.el.get(self.active_workflow().repository.RESOURCE_TEMPLATE_MODELS, {})
        models['hosts'] = []
        models['bundle'] = resource
        models['interfaces'] = {}
        models['vlans'] = {}
        for host in resource.getResources():
            models['hosts'].append(host)
            models['interfaces'][host.resource.name] = []
            models['vlans'][host.resource.name] = {}
            for interface in host.generic_interfaces.all():
                models['interfaces'][host.resource.name].append(interface)
                models['vlans'][host.resource.name][interface.profile.name] = []
                for vlan in interface.vlans.all():
                    models['vlans'][host.resource.name][interface.profile.name].append(vlan)
        return models

    def make_grb_confirm(self, resource):
        confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
        confirm['resource'] = {}
        confirm['resource']['hosts'] = []
        confirm['resource']['lab'] = resource.lab.lab_user.username
        for host in resource.getResources():
            confirm['resource']['hosts'].append({"name": host.resource.name, "profile": host.profile.name})
        return confirm

    def make_config_models(self, config):
        models = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIG_MODELS, {})
        models['bundle'] = config
        models['host_configs'] = []
        for host_conf in ResourceConfiguration.objects.filter(bundle=config):
            models['host_configs'].append(host_conf)
        models['opnfv'] = OPNFVConfig.objects.filter(bundle=config).last()
        return models

    def make_config_confirm(self, config):
        confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
        confirm['configuration'] = {}
        confirm['configuration']['hosts'] = []
        confirm['configuration']['name'] = config.name
        confirm['configuration']['description'] = config.description
        opnfv = OPNFVConfig.objects.filter(bundle=config).last()
        confirm['configuration']['installer'] = opnfv.installer.name
        confirm['configuration']['scenario'] = opnfv.scenario.name
        for host_conf in ResourceConfiguration.objects.filter(bundle=config):
            h = {"name": host_conf.host.resource.name, "image": host_conf.image.name, "role": host_conf.opnfvRole.name}
            confirm['configuration']['hosts'].append(h)
        return confirm

    def make_booking_models(self, booking):
        models = self.active_workflow().repository.el.get(self.active_workflow().repository.BOOKING_MODELS, {})
        models['booking'] = booking
        models['collaborators'] = []
        for user in booking.collaborators.all():
            models['collaborators'].append(user)
        return models

    def make_booking_confirm(self, booking):
        confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
        confirm['booking'] = {}
        confirm['booking']['length'] = (booking.end - booking.start).days
        confirm['booking']['project'] = booking.project
        confirm['booking']['purpose'] = booking.purpose
        confirm['booking']['resource name'] = booking.resource.template.name
        confirm['booking']['configuration name'] = booking.config_bundle.name
        confirm['booking']['collaborators'] = []
        for user in booking.collaborators.all():
            confirm['booking']['collaborators'].append(user.username)
        return confirm


class ManagerTracker():
    instance = None

    managers = {}

    def __init__(self):
        pass

    @staticmethod
    def getInstance():
        if ManagerTracker.instance is None:
            ManagerTracker.instance = ManagerTracker()
        return ManagerTracker.instance