import logging

from airavata.model.application.io.ttypes import DataType
from airavata.model.experiment.ttypes import ExperimentModel
from airavata.model.group.ttypes import ResourcePermissionType
from django.conf import settings

from airavata_django_portal_sdk import remoteapi
from airavata_django_portal_sdk.user_storage import api as user_storage

logger = logging.getLogger(__name__)


def get_experiment(request, experiment_id: str) -> ExperimentModel:
    return request.airavata_client.getExperiment(request.authz_token, experiment_id)


def launch(request, experiment_id):
    if remoteapi.is_remote_api_configured():
        resp = remoteapi.call(request,
                              "/experiments/{experiment_id}/launch/",
                              path_params={"experiment_id": experiment_id},
                              base_url="/api",
                              method="post")
        data = resp.json()
        if not data["success"]:
            logger.error(f"Failed to launch experiment {experiment_id}: {data['errorMessage']})", extra={'request': request})
            raise Exception(data["errorMessage"])
        return
    else:
        experiment = request.airavata_client.getExperiment(
            request.authz_token, experiment_id)
        _set_storage_id_and_data_dir(request, experiment)
        _move_tmp_input_file_uploads_to_data_dir(request, experiment)
        request.airavata_client.updateExperiment(
            request.authz_token, experiment_id, experiment)
        request.airavata_client.launchExperiment(
            request.authz_token, experiment_id, settings.GATEWAY_ID)


def clone(request, experiment_id):
    """Clone experiment and return the experiment id of the clone."""
    if remoteapi.is_remote_api_configured():
        resp = remoteapi.call(request,
                              "/experiments/{experiment_id}/clone/",
                              path_params={"experiment_id": experiment_id},
                              base_url="/api",
                              method="post")
        data = resp.json()
        return data['experimentId']
    else:
        # figure what project to clone into
        experiment = request.airavata_client.getExperiment(
            request.authz_token, experiment_id)
        project_id = _get_writeable_project(request, experiment)

        # clone experiment
        cloned_experiment_id = request.airavata_client.cloneExperiment(
            request.authz_token, experiment_id,
            "Clone of {}".format(experiment.experimentName), project_id)
        cloned_experiment = request.airavata_client.getExperiment(
            request.authz_token, cloned_experiment_id)

        # Create a copy of the experiment input files
        _copy_cloned_experiment_input_uris(request, cloned_experiment)

        # Null out experimentDataDir so a new one will get created at launch
        # time
        cloned_experiment.userConfigurationData.experimentDataDir = None
        request.airavata_client.updateExperiment(
            request.authz_token, cloned_experiment.experimentId, cloned_experiment
        )
        return cloned_experiment_id


def _set_storage_id_and_data_dir(request, experiment):
    # Storage ID
    experiment.userConfigurationData.storageId = user_storage.get_default_storage_resource_id(request)
    # Create experiment dir and set it on model
    if not experiment.userConfigurationData.experimentDataDir:
        project = request.airavata_client.getProject(
            request.authz_token, experiment.projectId)
        _, exp_dir = user_storage.create_user_dir(
            request,
            dir_names=(project.name, experiment.experimentName),
            create_unique=True)
        experiment.userConfigurationData.experimentDataDir = exp_dir
    else:
        # create_user_dir will also validate that absolute paths are
        # inside the user's storage directory
        _, exp_dir = user_storage.create_user_dir(
            request,
            path=experiment.userConfigurationData.experimentDataDir)
        experiment.userConfigurationData.experimentDataDir = exp_dir


def _move_tmp_input_file_uploads_to_data_dir(request, experiment):
    exp_data_dir = experiment.userConfigurationData.experimentDataDir
    for experiment_input in experiment.experimentInputs:
        if experiment_input.type == DataType.URI:
            if experiment_input.value:
                experiment_input.value = \
                    _move_if_tmp_input_file_upload(
                        request, experiment_input.value, exp_data_dir)
        elif experiment_input.type == DataType.URI_COLLECTION:
            data_product_uris = experiment_input.value.split(
                ",") if experiment_input.value else []
            moved_data_product_uris = []
            for data_product_uri in data_product_uris:
                moved_data_product_uris.append(
                    _move_if_tmp_input_file_upload(request, data_product_uri,
                                                   exp_data_dir))
            experiment_input.value = ",".join(moved_data_product_uris)


def _move_if_tmp_input_file_upload(
        request, data_product_uri, experiment_data_dir):
    """
    Conditionally moves tmp input file to data dir and returns new dp URI.
    """
    data_product = request.airavata_client.getDataProduct(
        request.authz_token, data_product_uri)
    if user_storage.is_input_file(
            request, data_product):
        moved_data_product = \
            user_storage.move(request, data_product=data_product, path=experiment_data_dir)
        return moved_data_product.productUri
    else:
        return data_product_uri


def _get_writeable_project(request, experiment):
    # figure what project to clone into:
    # 1) project of this experiment if writeable
    # 2) else, first writeable project
    project_id = experiment.projectId
    if _can_write(request, project_id):
        return project_id
    user_projects = request.airavata_client.getUserProjects(
        request.authz_token, settings.GATEWAY_ID, request.user.username, -1, 0)
    for user_project in user_projects:
        if _can_write(request, user_project.projectID):
            return user_project.projectID
    raise Exception(
        "Could not find writeable project for user {} in "
        "gateway {}".format(request.user.username, settings.GATEWAY_ID))


def _can_write(request, entity_id):
    return request.airavata_client.userHasAccess(
        request.authz_token, entity_id, ResourcePermissionType.WRITE)


def _copy_cloned_experiment_input_uris(request, cloned_experiment):
    # update the experimentInputs of type URI, copying input files into the
    # tmp input files directory of the data store
    for experiment_input in cloned_experiment.experimentInputs:
        # skip inputs without values
        if not experiment_input.value:
            continue
        if experiment_input.type == DataType.URI:
            cloned_data_product = _copy_experiment_input_uri(request,
                                                             experiment_input.value)
            if cloned_data_product is None:
                logger.warning("Setting cloned input {} to null".format(
                    experiment_input.name))
                experiment_input.value = None
            else:
                experiment_input.value = cloned_data_product.productUri
        elif experiment_input.type == DataType.URI_COLLECTION:
            data_product_uris = experiment_input.value.split(
                ",") if experiment_input.value else []
            cloned_data_product_uris = []
            for data_product_uri in data_product_uris:
                cloned_data_product = _copy_experiment_input_uri(request,
                                                                 data_product_uri)
                if cloned_data_product is None:
                    logger.warning(
                        "Omitting a cloned input value for {}".format(
                            experiment_input.name))
                else:
                    cloned_data_product_uris.append(
                        cloned_data_product.productUri)
            experiment_input.value = ",".join(cloned_data_product_uris)


def _copy_experiment_input_uri(request, data_product_uri):
    if user_storage.exists(request, data_product_uri=data_product_uri):
        return user_storage._copy_input_file(
            request, data_product_uri=data_product_uri)
    else:
        logger.warning("Could not find file for source data "
                       "product {}".format(data_product_uri))
        return None
