from contextlib import closing

from flask import make_response, request, abort
from flask.views import View

from pycs.database.Database import Database
from pycs.database.LabelProvider import LabelProvider
from pycs.database.Project import Project
from pycs.frontend.notifications.NotificationManager import NotificationManager
from pycs.jobs.JobGroupBusyException import JobGroupBusyException
from pycs.jobs.JobRunner import JobRunner
from pycs.util.LabelProviderUtil import load_from_root_folder as load_label_provider


class ExecuteLabelProvider(View):
    """
    execute the label provider associated with a passed project identifier
    """
    # pylint: disable=arguments-differ
    methods = ['POST']

    def __init__(self, db: Database, nm: NotificationManager, jobs: JobRunner):
        # pylint: disable=invalid-name
        self.db = db
        self.nm = nm
        self.jobs = jobs

    def dispatch_request(self, identifier):
        # extract request data
        data = request.get_json(force=True)

        if 'execute' not in data or data['execute'] is not True:
            return abort(400)

        # find project
        project = self.db.project(identifier)
        if project is None:
            return abort(404)

        # get label provider
        label_provider = project.label_provider()
        if label_provider is None:
            return abort(400)

        # execute label provider and add labels to project
        try:
            self.execute_label_provider(self.db, self.nm, self.jobs, project, label_provider)
        except JobGroupBusyException:
            return abort(400)

        return make_response()

    @staticmethod
    def execute_label_provider(db: Database, nm: NotificationManager, jobs: JobRunner,
                               project: Project, label_provider: LabelProvider):
        """
        start a job that loads and executes a label provider and saves its results to the
        database afterwards

        :param db: database object
        :param nm: notification manager object
        :param jobs: job runner object
        :param project: project
        :param label_provider: label provider
        :return:
        """

        # pylint: disable=invalid-name
        # receive loads and executes the given label provider
        def receive():
            with closing(load_label_provider(label_provider.root_folder)) as label_provider_impl:
                provided_labels = label_provider_impl.get_labels()
                return provided_labels

        # result adds the received labels to the database and fires events
        def result(provided_labels):
            with db:
                for label in provided_labels:
                    created_label, insert = project.create_label(label['name'], label['id'])

                    if insert:
                        nm.create_label(created_label)
                    else:
                        nm.edit_label(created_label)

        # run job with given functions
        jobs.run(project,
                 'Label Provider',
                 f'{project.name} ({label_provider.name})',
                 f'{project.identifier}/label-provider',
                 receive,
                 result=result)