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 db
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


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

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

    def dispatch_request(self, project_id: int):
        project = Project.get_or_404(project_id)

        # extract request data
        data = request.get_json(force=True)

        if not data.get('execute', False):
            abort(400, "execute flag is missing")

        # get label provider
        label_provider = project.label_provider
        if label_provider is None:
            abort(400, "This project does not have a label provider.")

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

        return make_response()

    @staticmethod
    def execute_label_provider(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 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(label_provider.load()) as label_provider_impl:
                return label_provider_impl.get_labels()

        project_id = project.id
        # result adds the received labels to the database and fires events
        def result(provided_labels):
            nonlocal project_id

            def _inner(labels, project_id):
                project = Project.query.get(project_id)
                labels = project.bulk_create_labels(labels)

                for label in labels:
                    nm.create_label(label)

            if db.session().in_transaction():
                _inner(provided_labels, project_id)
            else:
                with db.session.begin():
                    _inner(provided_labels, project_id)

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