from contextlib import closing

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

from pycs import app
from pycs import db
from pycs.database.LabelProvider import LabelProvider
from pycs.database.Project import Project
from pycs.database.Label import Label
from pycs.frontend.notifications.NotificationManager import NotificationManager
from pycs.jobs.JobGroupBusyException import JobGroupBusyException
from pycs.jobs.JobRunner import JobRunner


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


    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 = Project.query.get(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:
            ExecuteLabelProvider.execute_label_provider(project, label_provider)
        except JobGroupBusyException:
            return abort(400)

        return make_response()

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

        :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(label_provider.load()) as label_provider_impl:
                provided_labels = label_provider_impl.get_labels()
                return provided_labels

        project_id = project.id
        # result adds the received labels to the database and fires events
        def result(provided_labels):
            project = Project.query.get(project_id)
            created = []
            app.logger.info(f"Storing {len(provided_labels):,d} labels for project #{project_id}...")
            with db.session.begin_nested():
                for label in provided_labels:
                    created_label, is_new = project.create_label(commit=False, **label)
                    created.append((created_label, is_new))

            for label, is_new in created:
                notify = NotificationManager.created if is_new else NotificationManager.edited
                notify("label", label.id, Label)

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