// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"fmt"
	"sort"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"

	"github.com/pulumi/pulumi/pkg/v3/backend"
	"github.com/pulumi/pulumi/pkg/v3/backend/display"
	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
)

func newStackTagCmd() *cobra.Command {
	var stack string

	cmd := &cobra.Command{
		Use:   "tag",
		Short: "Manage stack tags",
		Long: "Manage stack tags\n" +
			"\n" +
			"Stacks have associated metadata in the form of tags. Each tag consists of a name\n" +
			"and value. The `get`, `ls`, `rm`, and `set` commands can be used to manage tags.\n" +
			"Some tags are automatically assigned based on the environment each time a stack\n" +
			"is updated.\n",
		Args: cmdutil.NoArgs,
	}

	cmd.PersistentFlags().StringVarP(
		&stack, "stack", "s", "", "The name of the stack to operate on. Defaults to the current stack")

	cmd.AddCommand(newStackTagGetCmd(&stack))
	cmd.AddCommand(newStackTagLsCmd(&stack))
	cmd.AddCommand(newStackTagRmCmd(&stack))
	cmd.AddCommand(newStackTagSetCmd(&stack))

	return cmd
}

func newStackTagGetCmd(stack *string) *cobra.Command {
	return &cobra.Command{
		Use:   "get <name>",
		Short: "Get a single stack tag value",
		Args:  cmdutil.SpecificArgs([]string{"name"}),
		Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
			name := args[0]

			opts := display.Options{
				Color: cmdutil.GetGlobalColorization(),
			}
			s, err := requireStack(*stack, false, opts, false /*setCurrent*/)
			if err != nil {
				return err
			}

			tags, err := backend.GetStackTags(commandContext(), s)
			if err != nil {
				return err
			}

			if value, ok := tags[name]; ok {
				fmt.Printf("%v\n", value)
				return nil
			}

			return errors.Errorf(
				"stack tag '%s' not found for stack '%s'", name, s.Ref())
		}),
	}
}

func newStackTagLsCmd(stack *string) *cobra.Command {
	var jsonOut bool
	cmd := &cobra.Command{
		Use:   "ls",
		Short: "List all stack tags",
		Args:  cmdutil.NoArgs,
		Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
			opts := display.Options{
				Color: cmdutil.GetGlobalColorization(),
			}
			s, err := requireStack(*stack, false, opts, true /*setCurrent*/)
			if err != nil {
				return err
			}

			tags, err := backend.GetStackTags(commandContext(), s)
			if err != nil {
				return err
			}

			if jsonOut {
				return printJSON(tags)
			}

			printStackTags(tags)
			return nil
		}),
	}

	cmd.PersistentFlags().BoolVarP(
		&jsonOut, "json", "j", false, "Emit output as JSON")

	return cmd
}

func printStackTags(tags map[apitype.StackTagName]string) {
	var names []string
	for n := range tags {
		names = append(names, n)
	}
	sort.Strings(names)

	rows := []cmdutil.TableRow{}
	for _, name := range names {
		rows = append(rows, cmdutil.TableRow{Columns: []string{name, tags[name]}})
	}

	cmdutil.PrintTable(cmdutil.Table{
		Headers: []string{"NAME", "VALUE"},
		Rows:    rows,
	})
}

func newStackTagRmCmd(stack *string) *cobra.Command {
	return &cobra.Command{
		Use:   "rm <name>",
		Short: "Remove a stack tag",
		Args:  cmdutil.SpecificArgs([]string{"name"}),
		Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
			name := args[0]

			opts := display.Options{
				Color: cmdutil.GetGlobalColorization(),
			}
			s, err := requireStack(*stack, false, opts, true /*setCurrent*/)
			if err != nil {
				return err
			}

			ctx := commandContext()

			tags, err := backend.GetStackTags(ctx, s)
			if err != nil {
				return err
			}

			delete(tags, name)

			return backend.UpdateStackTags(ctx, s, tags)
		}),
	}
}

func newStackTagSetCmd(stack *string) *cobra.Command {
	return &cobra.Command{
		Use:   "set <name> <value>",
		Short: "Set a stack tag",
		Args:  cmdutil.SpecificArgs([]string{"name", "value"}),
		Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
			name := args[0]
			value := args[1]

			opts := display.Options{
				Color: cmdutil.GetGlobalColorization(),
			}
			s, err := requireStack(*stack, false, opts, true /*setCurrent*/)
			if err != nil {
				return err
			}

			ctx := commandContext()

			tags, err := backend.GetStackTags(ctx, s)
			if err != nil {
				return err
			}

			if tags == nil {
				tags = make(map[apitype.StackTagName]string)
			}
			tags[name] = value

			return backend.UpdateStackTags(ctx, s, tags)
		}),
	}
}
