import collections

import click

from gradient import cliutils
from gradient import exceptions, clilogger, DEPLOYMENT_TYPES_MAP
from gradient.api_sdk import DeploymentsClient
from gradient.cli import common
from gradient.cli.cli import cli
from gradient.cli.cli_types import ChoiceType, json_string
from gradient.cli.common import api_key_option, del_if_value_is_none, ClickGroup, validate_comma_split_option
from gradient.commands import deployments as deployments_commands
from gradient.commands.deployments import DeploymentRemoveTagsCommand, DeploymentAddTagsCommand


@cli.group("deployments", help="Manage deployments", cls=ClickGroup)
def deployments():
    pass


@deployments.group("tags", help="Manage deployments tags", cls=ClickGroup)
def deployments_tags():
    pass


def get_deployment_client(api_key):
    deployment_client = DeploymentsClient(api_key=api_key, logger=clilogger.CliLogger(), ps_client_name="gradient-cli")
    return deployment_client


@deployments.command("create", help="Create new deployment")
@click.option(
    "--deploymentType",
    "deployment_type",
    type=ChoiceType(DEPLOYMENT_TYPES_MAP, case_sensitive=False),
    required=True,
    help="Model deployment type",
    cls=common.GradientOption,
)
@click.option(
    "--modelId",
    "model_id",
    required=True,
    help="ID of a trained model",
    cls=common.GradientOption,
)
@click.option(
    "--name",
    "name",
    required=True,
    help="Human-friendly name for new model deployment",
    cls=common.GradientOption,
)
@click.option(
    "--machineType",
    "machine_type",
    required=True,
    help="Type of machine for new deployment",
    cls=common.GradientOption,
)
@click.option(
    "--imageUrl",
    "image_url",
    required=True,
    help="Docker image for model serving",
    cls=common.GradientOption,
)
@click.option(
    "--instanceCount",
    "instance_count",
    type=int,
    required=True,
    help="Number of machine instances",
    cls=common.GradientOption,
)
@click.option(
    "--containerModelPath",
    "container_model_path",
    help="Container model path",
    cls=common.GradientOption,
)
@click.option(
    "--imageUsername",
    "image_username",
    help="Username used to access docker image",
    cls=common.GradientOption,
)
@click.option(
    "--imagePassword",
    "image_password",
    help="Password used to access docker image",
    cls=common.GradientOption,
)
@click.option(
    "--imageServer",
    "image_server",
    help="Docker image server",
    cls=common.GradientOption,
)
@click.option(
    "--containerUrlPath",
    "container_url_path",
    help="Container URL path",
    cls=common.GradientOption,
)
@click.option(
    "--endpointUrlPath",
    "endpoint_url_path",
    help="Endpoint URL path",
    cls=common.GradientOption,
)
@click.option(
    "--method",
    "method",
    help="Method",
    cls=common.GradientOption,
)
@click.option(
    "--dockerArgs",
    "docker_args",
    type=json_string,
    help="JSON-style list of docker args",
    cls=common.GradientOption,
)
@click.option(
    "--env",
    "env",
    type=json_string,
    help="JSON-style environmental variables map",
    cls=common.GradientOption,
)
@click.option(
    "--apiType",
    "api_type",
    help="Type of API",
    cls=common.GradientOption,
)
@click.option(
    "--ports",
    "ports",
    help="Ports",
    cls=common.GradientOption,
)
@click.option(
    "--clusterId",
    "cluster_id",
    help="Cluster ID",
    cls=common.GradientOption,
)
@click.option(
    "--authUsername",
    "auth_username",
    help="Username",
    cls=common.GradientOption,
)
@click.option(
    "--authPassword",
    "auth_password",
    help="Password",
    cls=common.GradientOption,
)
@click.option(
    "--auth",
    "generate_auth",
    is_flag=True,
    help="Generate username and password. Mutually exclusive with --authUsername and --authPassword",
    cls=common.GradientOption,
)
@click.option(
    "--tag",
    "tags",
    multiple=True,
    help="One or many tags that you want to add to experiment",
    cls=common.GradientOption
)
@click.option(
    "--tags",
    "tags_comma",
    help="Separated by comma tags that you want add to experiment",
    cls=common.GradientOption
)
@api_key_option
@common.options_file
def create_deployment(api_key, options_file, **kwargs):
    cliutils.validate_auth_options(kwargs)
    kwargs["tags"] = validate_comma_split_option(kwargs.pop("tags_comma"), kwargs.pop("tags"))
    del_if_value_is_none(kwargs)
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.CreateDeploymentCommand(deployment_client=deployment_client)
    command.execute(**kwargs)


DEPLOYMENT_STATES_MAP = collections.OrderedDict(
    (
        ("BUILDING", "Building"),
        ("PROVISIONING", "Provisioning"),
        ("STARTING", "Starting"),
        ("RUNNING", "Running"),
        ("STOPPING", "Stopping"),
        ("STOPPED", "Stopped"),
        ("ERROR", "Error"),
    )
)


@deployments.command("list", help="List deployments with optional filtering")
@click.option(
    "--state",
    "state",
    type=ChoiceType(DEPLOYMENT_STATES_MAP, case_sensitive=False),
    help="Filter by deployment state",
    cls=common.GradientOption,
)
@click.option(
    "--projectId",
    "project_id",
    help="Use to filter by project ID",
    cls=common.GradientOption,
)
@click.option(
    "--modelId",
    "model_id",
    help="Use to filter by model ID",
    cls=common.GradientOption,
)
@click.option(
    "--tag",
    "tags",
    multiple=True,
    cls=common.GradientOption,
    help="Filter by tags. Multiple use"
)
@api_key_option
@common.options_file
def get_deployments_list(api_key, options_file, **filters):
    del_if_value_is_none(filters)
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.ListDeploymentsCommand(deployment_client=deployment_client)
    try:
        command.execute(**filters)
    except exceptions.ApplicationError as e:
        clilogger.CliLogger().error(e)


@deployments.command("start", help="Start deployment")
@click.option(
    "--id",
    "id_",
    required=True,
    help="Deployment ID",
    cls=common.GradientOption,
)
@api_key_option
@common.options_file
def start_deployment(id_, options_file, api_key=None):
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.StartDeploymentCommand(deployment_client=deployment_client)
    command.execute(deployment_id=id_)


@deployments.command("stop", help="Stop deployment")
@click.option(
    "--id",
    "id_",
    required=True,
    help="Deployment ID",
    cls=common.GradientOption,
)
@api_key_option
@common.options_file
def stop_deployment(id_, options_file, api_key=None):
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.StopDeploymentCommand(deployment_client=deployment_client)
    command.execute(deployment_id=id_)


@deployments.command("delete", help="Delete deployment")
@click.option(
    "--id",
    "id_",
    required=True,
    help="Deployment ID",
    cls=common.GradientOption,
)
@api_key_option
@common.options_file
def delete_deployment(id_, options_file, api_key):
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.DeleteDeploymentCommand(deployment_client=deployment_client)
    command.execute(deployment_id=id_)


@deployments.command("update", help="Modify existing deployment")
@click.option(
    "--id",
    "deployment_id",
    required=True,
    help="ID of existing deployment",
    cls=common.GradientOption,
)
@click.option(
    "--deploymentType",
    "deployment_type",
    type=ChoiceType(DEPLOYMENT_TYPES_MAP, case_sensitive=False),
    help="Model deployment type",
    cls=common.GradientOption,
)
@click.option(
    "--modelId",
    "model_id",
    help="ID of a trained model",
    cls=common.GradientOption,
)
@click.option(
    "--name",
    "name",
    help="Human-friendly name for new model deployment",
    cls=common.GradientOption,
)
@click.option(
    "--machineType",
    "machine_type",
    help="Type of machine for new deployment",
    cls=common.GradientOption,
)
@click.option(
    "--imageUrl",
    "image_url",
    help="Docker image for model serving",
    cls=common.GradientOption,
)
@click.option(
    "--instanceCount",
    "instance_count",
    type=int,
    help="Number of machine instances",
    cls=common.GradientOption,
)
@click.option(
    "--containerModelPath",
    "container_model_path",
    help="Container model path",
    cls=common.GradientOption,
)
@click.option(
    "--imageUsername",
    "image_username",
    help="Username used to access docker image",
    cls=common.GradientOption,
)
@click.option(
    "--imagePassword",
    "image_password",
    help="Password used to access docker image",
    cls=common.GradientOption,
)
@click.option(
    "--imageServer",
    "image_server",
    help="Docker image server",
    cls=common.GradientOption,
)
@click.option(
    "--containerUrlPath",
    "container_url_path",
    help="Container URL path",
    cls=common.GradientOption,
)
@click.option(
    "--endpointUrlPath",
    "endpoint_url_path",
    help="Endpoint URL path",
    cls=common.GradientOption,
)
@click.option(
    "--method",
    "method",
    help="Method",
    cls=common.GradientOption,
)
@click.option(
    "--dockerArgs",
    "docker_args",
    type=json_string,
    help="JSON-style list of docker args",
    cls=common.GradientOption,
)
@click.option(
    "--env",
    "env",
    type=json_string,
    help="JSON-style environmental variables map",
    cls=common.GradientOption,
)
@click.option(
    "--apiType",
    "api_type",
    help="Type of API",
    cls=common.GradientOption,
)
@click.option(
    "--ports",
    "ports",
    help="Ports",
    cls=common.GradientOption,
)
@click.option(
    "--authUsername",
    "auth_username",
    help="Username",
    cls=common.GradientOption,
)
@click.option(
    "--authPassword",
    "auth_password",
    help="Password",
    cls=common.GradientOption,
)
@click.option(
    "--clusterId",
    "cluster_id",
    help="Cluster ID",
    cls=common.GradientOption,
)
@api_key_option
@common.options_file
def update_deployment(deployment_id, api_key, options_file, **kwargs):
    del_if_value_is_none(kwargs)
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.UpdateDeploymentCommand(deployment_client=deployment_client)
    command.execute(deployment_id, **kwargs)


@deployments.command("details", help="Get details of model deployment")
@click.option(
    "--id",
    "deployment_id",
    required=True,
    help="Deployment ID",
    cls=common.GradientOption,
)
@api_key_option
@common.options_file
def get_deployment(deployment_id, api_key, options_file):
    deployment_client = get_deployment_client(api_key)
    command = deployments_commands.GetDeploymentDetails(deployment_client=deployment_client)
    command.execute(deployment_id)


@deployments_tags.command("add", help="Add tags to deployment")
@click.option(
    "--id",
    "id",
    required=True,
    cls=common.GradientOption,
    help="ID of the deployment",
)
@click.option(
    "--tag",
    "tags",
    multiple=True,
    help="One or many tags that you want to add to deployment",
    cls=common.GradientOption
)
@click.option(
    "--tags",
    "tags_comma",
    help="Separated by comma tags that you want add to deployment",
    cls=common.GradientOption
)
@api_key_option
@common.options_file
def deployment_add_tag(id, options_file, api_key, **kwargs):
    kwargs["tags"] = validate_comma_split_option(kwargs.pop("tags_comma"), kwargs.pop("tags"), raise_if_no_tags=True)

    deployment_client = get_deployment_client(api_key)

    command = DeploymentAddTagsCommand(deployment_client=deployment_client)
    command.execute(id, **kwargs)


@deployments_tags.command("remove", help="Remove tags from deployment")
@click.option(
    "--id",
    "id",
    required=True,
    cls=common.GradientOption,
    help="ID of the deployment",
)
@click.option(
    "--tag",
    "tags",
    multiple=True,
    help="One or many tags that you want to remove from deployment",
    cls=common.GradientOption
)
@click.option(
    "--tags",
    "tags_comma",
    help="Separated by comma tags that you want to remove from deployment",
    cls=common.GradientOption
)
@api_key_option
@common.options_file
def deployment_remove_tags(id, options_file, api_key, **kwargs):
    kwargs["tags"] = validate_comma_split_option(kwargs.pop("tags_comma"), kwargs.pop("tags"), raise_if_no_tags=True)

    deployment_client = get_deployment_client(api_key)

    command = DeploymentRemoveTagsCommand(deployment_client=deployment_client)
    command.execute(id, **kwargs)
