import shutil
import uuid

from contextlib import closing
from pathlib import Path

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

from pycs import db
from pycs import settings
from pycs.database.LabelProvider import LabelProvider
from pycs.database.Model import Model
from pycs.database.Project import Project
from pycs.frontend.endpoints.projects.ExecuteExternalStorage import ExecuteExternalStorage
from pycs.frontend.endpoints.projects.ExecuteLabelProvider import ExecuteLabelProvider
from pycs.frontend.notifications.NotificationManager import NotificationManager
from pycs.jobs.JobRunner import JobRunner
from pycs.util.PipelineUtil import load_from_root_folder as load_pipeline


class CreateProject(View):
    """
    create a project, insert data into database and create directories
    """
    # 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):
        # extract request data
        data = request.get_json(force=True)

        name = data.get('name')
        description = data.get('description')

        if name is None:
            abort(400, "name argument is missing!")

        if description is None:
            abort(400, "description argument is missing!")


        model_id = int(data['model'])
        model = Model.get_or_404(model_id)

        label_provider_id = data.get('label')
        label_provider = None

        if label_provider_id is not None:
            label_provider = LabelProvider.get_or_404(label_provider_id)

        # create project folder
        project_folder = Path(settings.projects_folder, str(uuid.uuid1()))
        project_folder.mkdir()

        temp_folder = project_folder / 'temp'
        temp_folder.mkdir()

        # check project data directory
        if data['external'] is None:
            external_data = False
            data_folder = project_folder / 'data'
            data_folder.mkdir()

        else:
            external_data = True
            data_folder = Path(data['external'])

            # check if exists
            if not data_folder.exists():
                return abort(400, f"External folder does not exist: {data_folder}")

        # copy model to project folder
        model_folder = project_folder / 'model'
        shutil.copytree(model.root_folder, str(model_folder))

        with db.session.begin_nested():
            model, is_new = model.copy_to(
                name=f'{model.name} ({name})',
                root_folder=str(model_folder),
                commit=False)
            model.flush()

            if not is_new:
                abort(400, # pragma: no cover
                    f"Could not copy model! Model in \"{model_folder}\" already exists!")
            project = Project.new(name=name,
                                  description=description,
                                  model_id=model.id,
                                  label_provider_id=label_provider_id,
                                  root_folder=str(project_folder),
                                  external_data=external_data,
                                  data_folder=str(data_folder))

        # execute label provider and add labels to project
        if label_provider is not None:
            ExecuteLabelProvider.execute_label_provider(self.nm, self.jobs, project,
                                                        label_provider)

        # load model and add collections to the project
        def load_model_and_get_collections():
            with closing(load_pipeline(model.root_folder)) as pipeline:
                return pipeline.collections()

        def add_collections_to_project(provided_collections):
            with db.session.begin_nested():
                for position, collection in enumerate(provided_collections, 1):
                    project.create_collection(commit=False,
                                              position=position,
                                              **collection)

        self.jobs.run(project,
                      'Media Collections',
                      f'{project.name}',
                      f'{project.id}/media-collections',
                      executable=load_model_and_get_collections,
                      result=add_collections_to_project)

        # find media files
        if external_data:
            ExecuteExternalStorage.find_media_files(self.nm, self.jobs, project)

        # fire event
        self.nm.create_model(model)
        self.nm.create_project(project)

        # return success response
        return make_response()
