#!/usr/bin/env python3
"""snake is a build script with homage to cake, make, and rake."""

import json
import os
import requests
import subprocess
import sys
from typing import Callable, Dict, Tuple
import webbrowser

from wipac_dev_tools import __version__


def run(cmd) -> None:
    """Run the command and raise a CalledProcessError if the return code is nonzero."""
    subprocess.run(cmd).check_returncode()


def do_clean_task() -> None:
    """Remove directories and files generated in the build process."""
    run(["rm", "-fr", ".mypy_cache"])
    run(["rm", "-fr", ".pytest_cache"])
    run(["rm", "-fr", "build"])
    run(["rm", "-fr", "dist"])
    run(["rm", "-fr", "htmlcov"])
    run(["find", "wipac_dev_tools", "-name", "__pycache__", "-exec", "rm", "-fr", "{}", "+"])
    run(["find", "wipac_dev_tools", "-name", "__init__.pyc", "-exec", "rm", "-fr", "{}", "+"])
    run(["rm", "-fr", "wipac_dev_tools.egg-info"])
    run(["find", "tests", "-name", "__pycache__", "-exec", "rm", "-fr", "{}", "+"])
    run(["rm", "-fr", ".coverage"])


def do_check_task() -> None:
    """Check the versions of most dependencies against the latest from PyPi."""
    current_path = os.path.dirname(os.path.realpath(__file__))
    with open(os.path.join(current_path, 'requirements.txt')) as f:
        requirements_txt = f.read()
    requirements = requirements_txt.split("\n")
    for requirement in requirements:
        if "==" in requirement:
            equals_index = requirement.index("==")
            package_name = requirement[0:equals_index]
            package_version = requirement[equals_index+2:]
            url = f"https://pypi.python.org/pypi/{package_name}/json"
            r = requests.get(url)
            if r:
                data = json.loads(r.text)
                latest_version = data["info"]["version"]
                if package_version != latest_version:
                    print(f"{package_name} can be upgraded from {package_version} to {latest_version}")


def do_circleci_task() -> None:
    """Run a CircleCI build."""
    run(["circleci", "local", "execute", "--job", "test"])


def do_coverage_task() -> None:
    """Use pytest to generate a coverage report for our unit tests."""
    do_clean_task()
    run(["pytest", "--cov=wipac_dev_tools", "--cov-report=html", "--no-cov-on-fail", "tests"])
    webbrowser.open_new_tab("htmlcov/index.html")


def do_dist_task() -> None:
    """Build a distribution tarball and wheel for the project."""
    do_clean_task()
    do_rebuild_task()
    run(["python", "setup.py", "sdist", "bdist_wheel"])


def do_docker_task() -> None:
    """Build a Docker image for the project."""
    do_clean_task()
    do_rebuild_task()
    run(["docker", "build", "-t", f"wipac/wipac_dev_tools:{__version__}", "-f", "Dockerfile", "."])
    run(["docker", "image", "tag", f"wipac/wipac_dev_tools:{__version__}", "wipac/wipac_dev_tools:latest"])


def do_lint_task() -> None:
    """Run static analysis tools on the project."""
    flake8_cmd = ["flake8"]
    mypy_cmd = ["mypy", "--strict", "--allow-subclassing-any"]
    run(flake8_cmd + ["tests"])
    # TODO: Maybe when pytest has a mypy library stub
    # run(mypy_cmd + ["tests"])
    run(flake8_cmd + ["wipac_dev_tools"])
    run(mypy_cmd + ["wipac_dev_tools"])


def do_rebuild_task() -> None:
    """Remove old build caches, test, and lint the project."""
    do_clean_task()
    run(["pytest", "tests"])
    do_lint_task()


def do_test_task() -> None:
    """Remove old build caches and run unit tests on the project."""
    do_clean_task()
    run(["pytest", "tests"])

# ---------------------------------------------------------------------


TaskType = Tuple[str, str, Callable[[], None]]


def task(taskdef: TaskType) -> None:
    """Add a Task to the task table for this script."""
    tasks[taskdef[0]] = taskdef


tasks: Dict[str, TaskType] = {}

if __name__ == "__main__":
    # define tasks
    task(("clean", "Remove build cruft", do_clean_task))
    task(("check", "Check dependency package versions", do_check_task))
    task(("circleci", "Perform CI build and test", do_circleci_task))
    task(("coverage", "Perform coverage analysis", do_coverage_task))
    task(("dist", "Create a distribution tarball and wheel", do_dist_task))
    task(("docker", "Build a Docker image", do_docker_task))
    task(("lint", "Run static analysis tools", do_lint_task))
    task(("rebuild", "Test and lint the module", do_rebuild_task))
    task(("test", "Test the module", do_test_task))

    # if the user didn't supply a task
    if len(sys.argv) < 2:
        # provide a menu of tasks to run
        print("Try one of the following tasks:\n")
        taskList = list(tasks.keys())
        taskList.sort()
        for taskName in taskList:
            print(f"snake {taskName:20} # {tasks[taskName][1]}")
        sys.exit(1)

    # if the user supplied a task that wasn't defined
    if sys.argv[1] not in tasks:
        # tell them and show them how to get the menu
        print(f"No such task: {sys.argv[1]}\n\nTo see a list of all tasks/options, run 'snake'")
        sys.exit(1)

    # run the task function that the user identified
    tasks[sys.argv[1]][2]()
