# -*- coding: utf-8 -*-
## ---------------------------------------------------------------------------
## Copyright 2019 Dynatrace LLC
##
## 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.
## ---------------------------------------------------------------------------
"""convert compose.yaml files to compose.sh scripts

Usage:
```compose.sh
#!/bin/bash
# shell script generated by alyeska's compose-sh from the file below
# /Users/nick.vogt/dev/ci-alyeska/tests/compose/config-samples/compose-small.yaml

# numbers
pushd /Users/nick.vogt/dev/ci-alyeska/db/numbers > /dev/null
source activate ci-python36-2.1
python main.py
conda deactivate
popd > /dev/null

# calendar
pushd /Users/nick.vogt/dev/ci-alyeska/db/calendar > /dev/null
source activate python36-2019.8.20
python main.py
conda deactivate
popd > /dev/null

# time_period
# Uses:
# - numbers
# - calendar
pushd /Users/nick.vogt/dev/ci-alyeska/db/time_period > /dev/null
source activate ci-python36-2.1
python main.py
conda deactivate
popd > /dev/null
```
"""

import argparse
from collections import defaultdict
import logging
import pathlib
import time
from typing import Dict, Set

import yaml

from alyeska.compose import Task, Composer
from alyeska.compose.config import (
    parse_config,
    parse_tasks,
    parse_upstream_dependencies,
)


def get_docstring(compose_yaml: pathlib.Path) -> str:
    compose_yaml = pathlib.Path(compose_yaml)
    current_time_utc = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
    return (
        f"# shell script generated by alyeska's compose-sh from the file below\n"
        f"# {pathlib.Path(compose_yaml).resolve()}\n"
        f"# created at: {current_time_utc}"
    )


def comment_task(
    config: Dict,
    task: Task,
    inv_task_map: Dict[Task, str],
    dependency_map: Dict[Task, Set[Task]],
) -> str:
    """Comment the run command so it's easier to read

    Args:
        config (Dict): compose.yaml as dict
        task (Task): task to comment
        inv_task_map (Dict[Task, str]): [description]
        dependency_map (Dict[Task): [description]

    Returns:
        str: [description]
    """
    task_name = inv_task_map[task]
    uses_tasks = dependency_map[task]
    comments = [f"# {task_name}"]  # we'll "\n".join(comments) at the end
    if uses_tasks:
        comments.append("# Uses:")
        for upstream_task in uses_tasks:
            upstream_task_name = inv_task_map[upstream_task]
            comments.append(f"# - {upstream_task_name}")

    task_comment = "\n".join(comments)
    return task_comment


def get_tasks_blocks(compose_yaml: pathlib.Path) -> str:
    compose_yaml = pathlib.Path(compose_yaml)
    config = parse_config(compose_yaml)

    task_map = parse_tasks(config)
    inv_task_map = {v: k for k, v in task_map.items()}
    dependency_map = parse_upstream_dependencies(config)

    commands = []
    composer = Composer.from_yaml(compose_yaml)
    for _, tasks in composer.get_schedules().items():
        for task in tasks:
            comment = comment_task(
                config, task, inv_task_map=inv_task_map, dependency_map=dependency_map
            )
            cmd = "\n".join(
                [
                    f"pushd {task.loc.parent} > /dev/null",
                    f"source activate {task.env}",  # `conda activate env` is glitchy
                    f"python {task.loc.name}",
                    f"conda deactivate",  # `conda deactivate` is not, however...
                    "popd > /dev/null",
                ]
            )
            # ^^^^^^^^^^^^^^^^
            # /path/to/exe  \
            #     /path/to/loc
            block = "\n".join([comment, cmd])
            commands.append(block)

    tasks_block = "\n\n".join(commands)

    return tasks_block


def convert_yaml_to_sh(
    compose_yaml: pathlib.Path, ofile: pathlib.Path = None, shebang: str = "#!/bin/bash"
) -> str:
    """Export a compose.sh file based on yaml config

    Args:
        compose_yaml (pathlib.Path): Path to the config.yaml file
        ofile (pathlib.Path, optional): Where to export shell file. Defaults to None.

    Returns:
        str: output of str file
    """
    if ofile:
        ofile = pathlib.Path(ofile).resolve()

    docstring = get_docstring(compose_yaml)
    tasks_block = get_tasks_blocks(compose_yaml)
    trailing_newline = "\n"
    shell_script = "\n".join([shebang, docstring, "", tasks_block, trailing_newline])

    if ofile is not None:
        ofile.write_text(shell_script)

    return shell_script


def main():
    parser = argparse.ArgumentParser(
        description="Convert compose.yaml to compose.sh script"
    )
    parser.add_argument(
        "config_file",
        metavar="config_file",
        type=pathlib.Path,
        help="Python compose configuration file",
    )
    parser.add_argument(
        "-o", action="store", dest="ofile", default="compose.sh", type=pathlib.Path
    )
    parser.add_argument("-v", action="store_true", dest="verbose_output", default=False)
    args = parser.parse_args()

    output = convert_yaml_to_sh(args.config_file, ofile=args.ofile)

    if args.verbose_output:
        print(output)
