"""workflow commands for cmem command line interface."""
import datetime
import json
import sys
import time

import click

import requests

import timeago

from cmem.cmemc.cli import completion
from cmem.cmempy.workflow import get_workflows
from cmem.cmempy.workflow.workflow import (
    get_workflow_editor_uri,
)
from cmem.cmempy.workspace.activities import (
    ACTIVITY_TYPE_EXECUTE_LOCALWORKFLOW,
    VALID_ACTIVITY_STATUS
)
from cmem.cmempy.workspace.activities.taskactivities import (
    get_activities_status
)
from cmem.cmempy.workspace.activities.taskactivity import (
    get_activity_status,
    start_task_activity
)
from cmem.cmempy.workspace.projects.project import get_projects


def _workflows_get_ids():
    """Get a list of workflow IDs."""
    ids = []
    for project_desc in get_projects():
        for workflow_id in get_workflows(project_desc["name"]):
            ids.append(project_desc["name"] + ":" + workflow_id)
    return ids


def workflow_wait_until_finished(project_id, task_id, polling_interval):
    """Poll workflow status until workflow is finished and return status.

    :type project_id: string
    :type task_id: string
    :type polling_interval: int
    """
    while True:
        status = get_activity_status(project_id, task_id)
        # wait until isRunning is false
        if not status['isRunning']:
            break
        time.sleep(polling_interval)
    return status


def _status_command_pre_cmem_20_10(
        app, raw, _filter, workflow_ids
):
    """Get status of workflows without new taskActivitiesStatus API."""
    if workflow_ids == ():
        workflow_ids += tuple(_workflows_get_ids())

    for workflow_id in workflow_ids:
        project_id, task_id = workflow_id.split(":")
        status = get_activity_status(project_id, task_id)
        # filter for running activities
        if _filter == "running"\
                and 'isRunning' in status and not status['isRunning']:
            continue
        # filter for failed activities
        if _filter == "failed"\
                and "failed" in status and not status['failed']:
            continue
        # output raw json if wanted
        if raw:
            app.echo_info(json.dumps(status, indent=2))
        # output single line status
        else:
            app.echo_info("{} ... ".format(workflow_id), nl=False)
            workflow_echo_status(app, status)


def workflow_echo_status(app, status):
    """Print a colored status based on the returned JSON.

    Status can be Idle, Running, Canceling, Waiting, Finished
    isRunning is true for Running, Canceling, Waiting
    canceled only exists sometimes
    """
    # prepare human friendly relative time
    now = datetime.datetime.now()
    stamp = datetime.datetime.fromtimestamp(status["lastUpdateTime"] / 1000)
    time_ago = timeago.format(stamp, now, 'en')
    # prepare message
    if status["statusName"] == status["message"]:
        message = "{} ({})".format(
            status["statusName"],
            time_ago
        )
    else:
        message = "{} ({}, {})".format(
            status["statusName"],
            status["message"],
            time_ago)

    if status["isRunning"]:
        if status["statusName"] == "Running":
            app.echo_warning(message)
        elif status["statusName"] == "Canceling":
            app.echo_warning(message)
        elif status["statusName"] == "Waiting":
            app.echo_warning(message)
        else:
            raise ValueError(
                "statusName is %s, expecting one of: "
                "Running, Canceling or Waiting." % (status["statusName"])
            )
    else:
        # not running can be Idle or Finished
        if "failed" in status and status["failed"]:
            app.echo_error(message, nl=True, err=False)
        elif "cancelled" in status and status["cancelled"]:
            app.echo_warning(message)
        elif status["statusName"] == "Idle":
            app.echo_info(message)
        else:
            # Finished and without failed or canceled status
            app.echo_success(message)


@click.command(name="execute")
@click.option(
    "-a", "--all", "all_",
    is_flag=True,
    help="Execute all available workflows."
)
@click.option(
    "--wait",
    is_flag=True,
    help="Wait for one workflow to complete."
)
@click.option(
    "--polling-interval",
    type=click.IntRange(min=0, max=60),
    show_default=True,
    default=1,
    help="How many seconds to wait between status polls. Status polls are"
         " cheap, so a higher polling interval is most likely not needed."
)
@click.argument(
    "workflow_ids",
    nargs=-1,
    type=click.STRING,
    autocompletion=completion.workflow_ids
)
@click.pass_obj
def execute_command(app, all_, wait, polling_interval, workflow_ids):
    """Execute workflow(s).

    With this command, you can start one or more workflows at the same time or
    in a sequence, depending on the result of the predecessor.

    Executing a workflow can be done in two ways: Without --wait just sends
    the starting signal and does not look for the workflow and its result
    (fire and forget). Starting workflows in this way, starts all given
    workflows at the same time.

    The optional --wait option starts the workflows in the same way, but also
    polls the status of a workflow until it is finished. In case of an error of
    a workflow, the next workflow is not started.
    """
    all_workflow_ids = _workflows_get_ids()
    if workflow_ids == () and not all_:
        raise ValueError("Either specify at least one workflow or use the"
                         + " --all option to execute all workflows.")
    if all_:
        workflow_ids = all_workflow_ids

    for workflow_id in workflow_ids:
        if workflow_id not in all_workflow_ids:
            raise ValueError(
                "Workflow '{}' is not available.".format(workflow_id)
            )
        project_id, task_id = workflow_id.split(":")
        app.echo_info("{} ... ".format(workflow_id), nl=False)

        # before we start, we fetch the status
        status = get_activity_status(project_id, task_id)
        if not wait:
            if status['isRunning']:
                # in case of a running workflow, we only output status
                app.echo_info('Already Running')
            else:
                # in case of simple call, we just start and forget
                start_task_activity(project_id, task_id)
                app.echo_info('Started')
        else:
            # in case of --wait, we poll the status until finished
            if status['isRunning']:
                # in case of a running workflow, we only output status
                app.echo_info('Already Running ... ', nl=False)
            else:
                start_task_activity(project_id, task_id)
                app.echo_info('Started ... ', nl=False)

            status = workflow_wait_until_finished(
                project_id, task_id, polling_interval
            )
            # when we have a Finished status, we output it
            workflow_echo_status(app, status)
            # in case of failure, the following workflows are not executed
            if status['failed']:
                sys.exit(1)


@click.command(name="list")
@click.pass_obj
def list_command(app):
    """List available workflow ids."""
    for workflow_id in _workflows_get_ids():
        app.echo_info(workflow_id)


@click.command(name="status")
@click.option(
    "--project", "project_id",
    type=click.STRING,
    autocompletion=completion.project_ids,
    help="The project, from which you want to list the workflows. "
         "Project IDs can be listed with the 'project list' command."
)
@click.option(
    "--raw",
    is_flag=True,
    help="Output raw JSON info."
)
@click.option(
    "--filter", "_filter",
    type=click.Choice(VALID_ACTIVITY_STATUS, case_sensitive=False),
    help="Show only workflows of a specific status."
)
@click.argument(
    "workflow_ids",
    nargs=-1,
    type=click.STRING,
    autocompletion=completion.workflow_ids
)
@click.pass_obj
def status_command(app, project_id, raw, _filter, workflow_ids):
    """Get status information of workflow(s)."""
    try:
        workflow_status = get_activities_status(
            status=_filter,
            project_name=project_id,
            activity=ACTIVITY_TYPE_EXECUTE_LOCALWORKFLOW
        )
        for status in workflow_status:
            workflow_id = status["project"] + ":" + status["task"]
            if len(workflow_ids) == 0 or workflow_id in workflow_ids:
                if raw:
                    app.echo_info_json(status)
                else:
                    app.echo_info("{} ... ".format(workflow_id), nl=False)
                    workflow_echo_status(app, status)
    except requests.exceptions.HTTPError:
        app.echo_warning("CMEM 20.10 taskActivitiesStatus API not available.\n"
                         "Falling back to older and more slow API with less "
                         "filter options.")
        _status_command_pre_cmem_20_10(app, raw, _filter, workflow_ids)


@click.command(name="open")
@click.argument(
    "workflow_id",
    type=click.STRING,
    autocompletion=completion.workflow_ids
)
@click.pass_obj
def open_command(app, workflow_id):
    """Open a workflow in your browser."""
    project_id, task_id = workflow_id.split(":")
    workflow_editor_uri = get_workflow_editor_uri()\
        .format(project_id, task_id)
    click.launch(workflow_editor_uri)
    app.echo_debug(workflow_editor_uri)


@click.group()
def workflow():
    """List, execute, open or inspect workflows.

    Workflows are identified by a WORKFLOW_ID. The get a list of existing
    workflows, execute the list command or use tab-completion.
    The WORKFLOW_ID is a concatenation of an PROJECT_ID and a TASK_ID, such as
    "my-project:my-workflow".
    """


workflow.add_command(execute_command)
workflow.add_command(list_command)
workflow.add_command(status_command)
workflow.add_command(open_command)
