# -*- coding: utf-8 -*-
#
# Copyright 2018-2022- Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Wrap CWL runner."""

import os
import shutil
import sys
from urllib.parse import unquote

import click

from renku.command.util import progressbar
from renku.core.errors import WorkflowRerunError
from renku.core.util.os import expand_directories
from renku.domain_model.project_context import project_context


def execute(output_file, output_paths=None):
    """Run the generated workflow using cwltool library.

    Args:
        output_file: Path to CWL file.
        output_paths: List of generated paths (Default value = None).
    """
    output_paths = output_paths or set()

    import cwltool.factory
    from cwltool import workflow
    from cwltool.context import LoadingContext, RuntimeContext
    from cwltool.utils import visit_class

    def construct_tool_object(toolpath_object, *args, **kwargs):
        """Fix missing locations."""
        protocol = "file://"

        def addLocation(d):
            if "location" not in d and "path" in d:
                d["location"] = protocol + d["path"]

        visit_class(toolpath_object, ("File", "Directory"), addLocation)
        return workflow.default_make_tool(toolpath_object, *args, **kwargs)

    argv = sys.argv
    sys.argv = ["cwltool"]

    # Keep all environment variables.
    runtime_context = RuntimeContext(
        kwargs={"rm_tmpdir": False, "move_outputs": "leave", "preserve_entire_environment": True}
    )
    loading_context = LoadingContext(kwargs={"construct_tool_object": construct_tool_object, "relax_path_checks": True})

    factory = cwltool.factory.Factory(loading_context=loading_context, runtime_context=runtime_context)
    process = factory.make(os.path.relpath(str(output_file)))
    try:
        outputs = process()
    except cwltool.factory.WorkflowStatus:
        raise WorkflowRerunError(output_file)

    sys.argv = argv

    # Move outputs to correct location in the repository.
    output_dirs = process.factory.executor.output_dirs

    def remove_prefix(location, prefix="file://"):
        if location.startswith(prefix):
            return unquote(location[len(prefix) :])
        return unquote(location)

    locations = {remove_prefix(output["location"]) for output in outputs.values()}  # type: ignore
    # make sure to not move an output if it's containing directory gets moved
    locations = {
        location for location in locations if not any(location.startswith(d) for d in locations if location != d)
    }

    with progressbar(locations, label="Moving outputs") as bar:
        for location in bar:
            for output_dir in output_dirs:
                if location.startswith(output_dir):
                    output_path = location[len(output_dir) :].lstrip(os.path.sep)
                    destination = project_context.path / output_path
                    if destination.is_dir():
                        shutil.rmtree(str(destination))
                        destination = destination.parent
                    shutil.move(location, str(destination))
                    continue

    unchanged_paths = remove_unmodified(output_paths)
    if unchanged_paths:
        click.echo(
            "Unchanged files:\n\n\t{0}".format("\n\t".join(click.style(path, fg="yellow") for path in unchanged_paths))
        )


def remove_unmodified(paths):
    """Remove unmodified paths and return their names."""
    tested_paths = set(expand_directories(paths))

    # Keep only unchanged files in the output paths.
    tracked_paths = {
        d.b_path
        for d in project_context.repository.unstaged_changes
        if d.change_type in {"A", "R", "M", "T"} and d.b_path in tested_paths
    }
    unchanged_paths = tested_paths - tracked_paths

    return unchanged_paths
