"""
Pytest plugin
------------------

This is a pytest plugin that is used by workflow CI jobs for automatically testing against a live server.

"""
from nextcode.credentials import generate_credential_struct, creds_to_dict
from nextcode.packagelocal import package_and_upload
from nextcode import get_service

from urllib.parse import urlsplit

import logging
import time
import boto3
import os
import posixpath
import tempfile
import pytest
import sys
import uuid
import pprint


def pytest_addoption(parser):
    group = parser.getgroup("nc-wf")
    group.addoption(
        "--project",
        action="store",
        dest="project",
        help="Sets the name of the project to run the pipeline in",
        default="no_project",
    )
    group.addoption(
        "--runid",
        action="store",
        dest="run_id",
        help="The name/id of the folder the pipeline result_dir will be stored in",
        default=str(uuid.uuid4()),
    )
    group.addoption(
        "--runmode",
        action="store",
        dest="run_mode",
        type=str,
        choices=["repository", "local"],
        help="Specifies if you want to run your local copy of the pipeline, or a version from a remote git repository",
        default="local",
    )
    group.addoption(
        "--revision",
        action="store",
        dest="revision",
        help="Specifies what revision should be run of the pipeline repository",
    )
    group.addoption(
        "--repository",
        action="store",
        dest="repository",
        help="Specifies the git repository containing the pipeline",
    )
    group.addoption(
        "--localdir",
        action="store",
        dest="local_dir",
        help="Specifies what directory contains the pipeline",
        default=".",
    )
    group.addoption(
        "--profile",
        action="store",
        dest="profile",
        help="Specifies the default profile used to run the pipeline",
        default="test",
    )
    group.addoption(
        "--use-last-run",
        action="store_true",
        dest="use_last_run",
        help="If specified the wf plugin will try to use the output from the last run instead of starting a new one",
    )
    group.addoption(
        "--storage_type",
        action="store",
        dest="storage_type",
        help="Specifies the storage type to use for the pipeline run",
        default=None,
    )
    group.addoption(
        "--credentials",
        action="append",
        nargs="+",
        dest="credentials",
        help="Specifies the credentials to forward to workflow-service",
        default=[],
    )
    group.addoption(
        "--scheduler_name",
        action="store",
        dest="scheduler_name",
        help="Specifies the scheduler to use in the pipeline run",
        default=None,
    )
    parser.addini(
        "base_upload_bucket",
        "The S3 location the result_dir parameter of the pipeline will be set to",
        default="s3://nextcode-scratch/workflow-ci",
    )


# Let's suppress the wild logging of these libraries
logging.getLogger("requests.packages.urllib3.connectionpool").setLevel(logging.WARNING)
logging.getLogger("boto3").setLevel(logging.INFO)
logging.getLogger("botocore").setLevel(logging.WARNING)
logging.getLogger("s3transfer").setLevel(logging.INFO)
logging.getLogger("nextcode").setLevel(logging.INFO)
log = logging.getLogger(__name__)

start_time = 0


def pytest_report_header(config, startdir):

    return (
        "Running workflow in project '%s'. This can take some time so going for a coffee is not a bad idea :)"
        % config.option.project
    )


def print_status(txt, newline=True, stamp=True):
    d = time.time() - start_time
    if newline and stamp:
        print("[%.2fs] %s" % (d, txt), flush=True)
    elif newline:
        print(txt, flush=True)
    else:
        print(txt, flush=True, end="")


@pytest.fixture(scope="class", autouse=False)
def run_workflow(request):
    svc = get_service("workflow")
    global start_time
    start_time = time.time()
    class_id = request.cls.__name__
    print_status("WF plugin starting.")
    # Check if we should use the files generated by last run instead of starting a new one.
    last_run_key = "wf_plugin/latest_result_dir/{}".format(class_id)
    use_last_run = request.config.option.use_last_run
    if use_last_run:
        print_status(
            "--use-last-run flag set. Using output files from the previous run."
        )
        prev_dir = request.config.cache.get(last_run_key, None)
        if prev_dir:
            print_status("Previous file dir found: %s." % prev_dir)
            # use the previous directory if it exists
            if os.path.exists(prev_dir):
                request.cls.result_dir = prev_dir
                return
            # previous directory doesn't exists.  Remove the key and continue with the run.
            print_status(
                "WARN: '%s' doesn't exist anymore.  Rerunning workflow." % prev_dir
            )
            request.config.cache.set(last_run_key, None)
        else:
            print_status("Previous file dir not found. Starting a new run.")

    base_result_dir = request.config.getini("base_upload_bucket")
    project_name = request.config.option.project
    run_mode = request.config.option.run_mode
    run_id = request.config.option.run_id
    storage_type = request.config.option.storage_type
    credentials_args = request.config.option.credentials
    scheduler_name = request.config.option.scheduler_name
    # Use the profile specified by the TestCase if it exists. Use the default profile if none is provided.
    profile = getattr(request.cls, "profile", request.config.option.profile)
    revision = None

    if run_mode == "repository":
        build_context = request.config.option.repository
        revision = request.config.option.revision
        build_source = "git"
        if not build_context:
            print("ERROR: Your must specify a --repository when --runmode = repository")
            sys.exit(-1)

    else:
        path = os.path.abspath(os.path.expanduser(request.config.option.local_dir))
        p = package_and_upload(svc, "local_workflow", path)
        build_source = "url"
        build_context = p

    # Add the parameters if the TestCase defines them
    params = getattr(request.cls, "params", {})

    # add result_dir
    result_dir = "{prefix}/{run}/{instance}/".format(
        prefix=base_result_dir, run=run_id, instance=class_id
    )
    params["result_dir"] = result_dir
    print_status(
        "Workflow run parameters:\n\tWorkflow repository: %s:%s\n\tProject: %s\n\tProfile: %s\n\tParameters: %s"
        % (
            build_context,
            revision,
            project_name,
            profile,
            pprint.pformat(params, width=-1),
        )
    )
    cred_map = creds_to_dict([x[0] for x in credentials_args])
    credentials = generate_credential_struct(cred_map)
    job = svc.post_job(
        None,
        project_name,
        params,
        build_context,
        revision,
        build_source=build_source,
        build_context=build_context,
        profile=profile,
        storage_type=storage_type,
        credentials=credentials,
        scheduler_name=scheduler_name,
    )

    last_status = ""
    while job.running:
        time.sleep(5)
        job.refresh()
        if last_status == job.status:
            print_status(".", newline=False)
        else:
            print_status(
                "\nJob %s status = %s" % (job.job_id, job.status), newline=False
            )
        last_status = job.status

    print_status(
        "\nJob %s is no longer running. Status = %s" % (job.job_id, job.status)
    )

    if not job.status == "COMPLETED":
        print_status(job.logs("pod", ""))
        raise Exception("Job did not complete successfully")

    temp_dir = tempfile.mkdtemp()

    _download_all(result_dir, temp_dir)

    request.cls.result_dir = temp_dir
    # Store the latest result dir in cache
    request.config.cache.set(last_run_key, temp_dir)
    print_status("WF plugin run finished.")


def _download_all(s3_path, local_path):
    """Download all files from s3_path into the local folder"""
    s3 = boto3.resource("s3")
    parts = urlsplit(s3_path)
    bucket_name = parts.netloc
    bucket = s3.Bucket(bucket_name)  # pylint: disable=no-member
    print_status(
        "\nDownloading from '{}' to '{}':".format(s3_path, local_path), newline=False
    )
    root_remote_dir = parts.path[1:]

    try:
        os.makedirs(local_path)
    except IOError:
        pass

    for f in bucket.objects.filter(Prefix=root_remote_dir):

        if f.key[-1] == "/":  # skip directories
            continue

        # Get the relative name of the object
        rel_path = posixpath.relpath(f.key, root_remote_dir)

        local_file = os.path.join(local_path, rel_path)
        try:
            os.makedirs(os.path.dirname(local_file))
        except IOError:
            pass
        log.info("Downloading file %s" % f.key)
        print_status(".", newline=False)
        bucket.download_file(f.key, local_file)
