from contextlib import closing
from os import mkdir
from os import path
from pathlib import Path
from shutil import copytree
from uuid import uuid1

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.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']


    @property
    def project_folder(self):
        return app.config["TEST_PROJECTS_DIR"] if app.config["TESTING"] else 'projects'

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

        if None in [data.get('name'), data.get('description')]:
            return abort(400, "name and description information missing!")

        name = data['name']
        description = data['description']
        model_id = data['model']
        label_provider_id = data['label']
        data_folder = data['external']
        external_data = data_folder is not None

        # find model
        model = Model.query.get(model_id)

        if model is None:
            return abort(404, "Model not found")

        # find label provider
        if label_provider_id is None:
            label_provider = None
        else:
            label_provider = LabelProvider.query.get(label_provider_id)

            if label_provider is None:
                return abort(404, "Label provider not found")

        # create project folder
        project_folder = Path(self.project_folder, str(uuid1()))
        project_folder.mkdir(parents=True)

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

        # check project data directory
        if external_data:
            # check if exists
            if not path.exists(data_folder):
                return abort(400, "Data folder does not exist!")

        else:
            data_folder = project_folder / 'data'
            data_folder.mkdir()


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

        model, _ = model.copy_to(f'{model.name} ({name})', str(model_folder))

        # create entry in database
        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(project, label_provider)

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

        project_id = project.id
        def add_collections_to_project(provided_collections):
            project = Project.query.get(project_id)
            with db.session.begin_nested():
                for position, collection in enumerate(provided_collections):
                    project.create_collection(
                        collection['reference'],
                        collection['name'],
                        collection['description'],
                        position + 1,
                        collection['autoselect'],
                        commit=False,
                    )

        JobRunner.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(project)

        # fire event
        NotificationManager.created("model", model.id, Model)
        NotificationManager.created("project", project.id, Project)

        # return success response
        return make_response()