package configuration

import (
	"errors"
	"fmt"

	"code.justin.tv/d8a/buddy/cmd/buddy-cli/data"
	"code.justin.tv/d8a/buddy/lib/clusters"
	"code.justin.tv/d8a/buddy/lib/clusters/clusterdb"
	"code.justin.tv/d8a/buddy/lib/config"
	"code.justin.tv/d8a/buddy/lib/store"
	"code.justin.tv/d8a/buddy/lib/terminal"
	"code.justin.tv/systems/sandstorm/manager"
	"github.com/spf13/cobra"
)

var roleService = &cobra.Command{
	Use:   "role-service [cluster] [role name] [service name] <optional: [user secret] [password secret]>",
	Short: "Add a new service to a rolepair",
	Long:  "Add a new service to a rolepair",
	RunE: func(command *cobra.Command, args []string) error {
		if len(data.ConfigureData.Args()) != 2 && len(data.ConfigureData.Args()) != 4 {
			return command.Usage()
		}

		role := data.ConfigureData.Args()[0]
		service := data.ConfigureData.Args()[1]
		sandstormClient := data.ConfigureData.SandstormClient()
		foundCluster := data.ConfigureData.FoundCluster()
		var userSecret, passwordSecret string

		if len(data.ConfigureData.Args()) > 2 {
			userSecret = data.ConfigureData.Args()[2]
			passwordSecret = data.ConfigureData.Args()[3]
		} else {
			userSecret = sandstormClient.DefaultSecretPath(foundCluster.Name, fmt.Sprintf("svc-%s-user", service))
			passwordSecret = sandstormClient.DefaultSecretPath(foundCluster.Name, fmt.Sprintf("svc-%s-password", service))
		}

		//Get the current user & password
		rolePair := foundCluster.GetRolePair(role)
		if rolePair == nil {
			return fmt.Errorf("There is no role pair named %s in cluster %s", role, foundCluster.Name)
		}

		clusterDb, err := clusterdb.OpenDbConn(foundCluster, sandstormClient, foundCluster.SuperUser)
		if err != nil {
			return err
		}

		if !clusterDb.CanUseRolePairs() {
			return fmt.Errorf("can't add a role service to cluster %s because the backing database doesn't support role pairs", foundCluster.Name)
		}

		userSecretExists := sandstormClient.SecretExists(userSecret)
		passwordSecretExists := sandstormClient.SecretExists(passwordSecret)

		if userSecretExists != passwordSecretExists {
			var secretExists, secretDoesntExist string
			if userSecretExists {
				secretExists = userSecret
				secretDoesntExist = passwordSecret
			} else {
				secretExists = passwordSecret
				secretDoesntExist = userSecret
			}

			return fmt.Errorf("the service secret '%s' exists, but the service secret '%s' doesn't- this is a deeply confusing situation and you should correct it", secretExists, secretDoesntExist)
		}

		if !userSecretExists {
			createSecrets, err := terminal.AskForConfirmation(fmt.Sprintf("The secrets '%s' and '%s' don't exist in sandstorm.  Create them?", userSecret, passwordSecret))
			if err != nil {
				return err
			}

			if !createSecrets {
				return errors.New("create the secrets and try again")
			}

			//Find the live user for the role pair
			allUsers, err := clusterDb.ClusterUsers()
			if err != nil {
				return err
			}

			var bestUser *config.User
			for _, user := range rolePair.Users {
				canlogin, ok := allUsers[user.Name]
				if canlogin && ok {
					if bestUser != nil {
						fmt.Println("Multiple users in role pair %s are unlocked.  Locating the unused user and locking it...", rolePair.Name)
						setUser, err := clusters.FixRoleKeys(clusterDb, rolePair)
						if err != nil {
							return err
						}

						if setUser == user.Name {
							bestUser = user
						}

						break
					}
					bestUser = user
				}
			}

			if bestUser == nil {
				return fmt.Errorf("there are no unlocked users in role pair %s, which means good service secrets can't be built", rolePair.Name)
			}

			servicePassword, err := sandstormClient.GetUserPassword(foundCluster, bestUser.Name)
			if err != nil {
				return err
			}

			//Write user & password
			err = sandstormClient.Manager().Put(&manager.Secret{
				Name:      userSecret,
				Plaintext: []byte(bestUser.Name),
			})
			if err != nil {
				return err
			}

			err = sandstormClient.Manager().Put(&manager.Secret{
				Name:      passwordSecret,
				Plaintext: []byte(servicePassword),
			})
			if err != nil {
				return err
			}
		}

		roleService := rolePair.GetService(service)
		if roleService != nil {
			roleService.PasswordSecret = passwordSecret
			roleService.UsernameSecret = userSecret
			return store.UpdateRoleService(data.ConfigureData.StoreDB(), roleService)
		}

		return store.InsertRoleService(data.ConfigureData.StoreDB(), rolePair, &config.RoleService{
			Name:           service,
			UsernameSecret: userSecret,
			PasswordSecret: passwordSecret,
		})
	},
}

func init() {
	ConfigureCmd.AddCommand(roleService)
}
