#!/usr/bin/env python -u
# flake8: noqa: F811
import yaml
import json
import click
import pkg_resources
import cowait.cli.commands
from cowait.cli import CliError
from cowait.cli.config import CowaitConfig


def option_val(val):
    try:
        return json.loads(val)
    except json.JSONDecodeError:
        return val


def option_dict(opts):
    options = {}
    for [key, val] in opts:
        options[key] = option_val(val)
    return options


@click.group()
@click.version_option(pkg_resources.require("cowait")[0].version)
@click.pass_context
def cli(ctx):
    pass


#
# context commands
#


@cli.command(help='create a new context')
@click.argument('name', type=str, required=False)
@click.option('--image', type=str, required=False, help='image name')
@click.option('--base', type=str, required=False, help='base image name')
@click.option('--cluster',
              default=None,
              type=str,
              help='cluster name')
def new_context(name: str, image: str, base: str, cluster_name: str):
    cowait.cli.new_context(
        name=name,
        image=image,
        base=base,
        cluster_name=cluster_name,
    )


#
# task commands
#


@cli.command(help='run a task')
@click.argument('task', type=str)
@click.option('-c', '--cluster',
              default=None,
              type=str,
              help='cluster name')
@click.option('--name',
              type=str,
              default=None,
              help='specific task name')
@click.option('-i', '--input',
              type=(str, str),
              multiple=True,
              help='specify task input')
@click.option('-e', '--env',
              type=(str, str),
              multiple=True,
              help='define enviornment variable')
@click.option('--port', type=int, multiple=True, help='open a port')
@click.option('--route',
              type=(str, str),
              multiple=True,
              help='add an ingress route')
@click.option('--upstream',
              type=str,
              help='root task upstream uri')
@click.option('-b', '--build',
              type=bool, is_flag=True,
              help='build and push first',
              default=False)
@click.option('-d', '--detach',
              type=bool, is_flag=True,
              help='run in detached mode',
              default=False)
@click.option('-f', '--json', '--yml', '--yaml', 'file', 
              help='yaml/json file with inputs', 
              type=str, 
              default=None)
@click.pass_context
def run(
    ctx, task: str, cluster: str, name: str,
    input, env, port, route,
    upstream: str, build: bool, detach: bool,
    file: str,
):
    if cluster is not None:
        ctx.obj.default_cluster = cluster

    file_inputs = {}
    if file is not None:
        try:
            with open(file, 'r') as f:
                file_inputs = yaml.load(f, Loader=yaml.FullLoader)
        except yaml.parser.ParserError as e:
            raise CliError(f'Error in {file}: {e}')

    if not isinstance(file_inputs, dict):
        raise CliError('Error: Expected input file to contain a dictionary')

    cowait.cli.run(
        ctx.obj,
        task,
        name=name,
        inputs={
            **file_inputs,
            **option_dict(input),
        },
        env=option_dict(env),
        ports={p: p for p in port},
        routes=option_dict(route),
        upstream=upstream,
        build=build,
        detach=detach,
    )


@cli.command(help='run task tests')
@click.option('-c', '--cluster',
              default=None,
              type=str,
              help='cluster name')
@click.option('--push',
              type=bool, is_flag=True,
              help='build and push first',
              default=False)
@click.pass_context
def test(ctx, cluster: str, push: bool):
    if cluster is not None:
        ctx.obj.default_cluster = cluster
    cowait.cli.test(ctx.obj, push)


@cli.command(help='destroy tasks')
@click.option('-c', '--cluster',
              default=None,
              type=str,
              help='cluster name')
@click.pass_context
def rm(ctx, cluster: str):
    if cluster is not None:
        ctx.obj.default_cluster = cluster
    cowait.cli.destroy(ctx.obj)


@cli.command(help='list tasks')
@click.option('-c', '--cluster',
              default=None,
              type=str,
              help='cluster name')
@click.pass_context
def ps(ctx, cluster: str):
    if cluster is not None:
        ctx.obj.default_cluster = cluster
    cowait.cli.list_tasks(ctx.obj)


@cli.command(help='kill task')
@click.option('-c', '--cluster',
              default=None,
              type=str,
              help='cluster name')
@click.argument('task', type=str)
@click.pass_context
def kill(ctx, cluster: str, task: str):
    if cluster is not None:
        ctx.obj.default_cluster = cluster
    cowait.cli.kill(ctx.obj, task)


@cli.command(help='deploy cowait agent')
@click.option('-c', '--cluster',
              default=None,
              type=str,
              help='cluster name')
@click.option('-d', '--detach',
              type=bool, is_flag=True,
              help='run in detached mode',
              default=False)
@click.option('-u', '--upstream',
              type=str, default=None,
              help='custom upstream uri')
@click.pass_context
def agent(ctx, cluster: str, detach: bool, upstream: str):
    if cluster is not None:
        ctx.obj.default_cluster = cluster
    cowait.cli.agent(ctx.obj, detach, upstream)


#
# task image commands
#


@cli.command(help='build a task')
def build():
    cowait.cli.build()


@cli.command(help='push a task to the registry')
def push():
    cowait.cli.push()


#
# cluster subcommand
#


@cli.group(help='cluster management')
@click.pass_context
def cluster(ctx):
    pass


@cluster.command(help='describe cluster')
@click.argument('name', type=str)
@click.pass_context
def get(ctx, name: str):
    cowait.cli.cluster_get(ctx.obj, name)


@cluster.command(help='list all clusters')
@click.pass_context
def ls(ctx):
    cowait.cli.cluster_ls(ctx.obj)


@cluster.command(help='default cluster name')
@click.pass_context
def default(ctx):
    cowait.cli.cluster_default(ctx.obj)


@cluster.command(help='set default cluster')
@click.argument('name', type=str)
@click.pass_context
def set_default(ctx, name: str):
    cowait.cli.cluster_set_default(ctx.obj, name)


@cluster.command(help='add new cluster')
@click.argument('name', type=str)
@click.option('--type', type=str, help='cluster provider type')
@click.option('-o', '--option',
              type=(str, str),
              multiple=True,
              help='specify cluster provider option')
@click.pass_context
def add(ctx, name: str, type: str, option: dict = {}):
    cowait.cli.cluster_add(ctx.obj, name, type.lower(), **option_dict(option))


@cluster.command(help='remove cluster')
@click.argument('name', type=str)
@click.pass_context
def rm(ctx, name: str):
    cowait.cli.cluster_rm(ctx.obj, name)


if __name__ == '__main__':
    config = CowaitConfig.load()
    try:
        cli(obj=config)
    except CliError as e:
        print(f'Error: {e}')
