package cmd

import (
	"errors"
	"net/url"

	"github.com/Infisical/infisical-merge/packages/config"
	"github.com/Infisical/infisical-merge/packages/models"
	"github.com/Infisical/infisical-merge/packages/util"
	"github.com/manifoldco/promptui"
	"github.com/posthog/posthog-go"
	"github.com/spf13/cobra"
)

var userCmd = &cobra.Command{
	Use:                   "user",
	Short:                 "Used to manage user credentials",
	DisableFlagsInUseLine: true,
	Example:               "infisical user",
	Args:                  cobra.ExactArgs(0),
	Run: func(cmd *cobra.Command, args []string) {
		cmd.Help()
	},
}

var switchCmd = &cobra.Command{
	Use:                   "switch",
	Short:                 "Used to switch between Infisical profiles",
	DisableFlagsInUseLine: true,
	Example:               "infisical switch",
	Args:                  cobra.ExactArgs(0),
	PreRun: func(cmd *cobra.Command, args []string) {
		util.RequireLogin()
	},
	Run: func(cmd *cobra.Command, args []string) {
		//get previous logged in profiles
		loggedInProfiles, err := getLoggedInUsers()
		if err != nil {
			util.HandleError(err, "[infisical user switch]: Unable to get logged Profiles")
		}

		//prompt user
		profile, err := LoggedInUsersPrompt(loggedInProfiles)
		if err != nil {
			util.HandleError(err, "[infisical user switch]: Prompt error")
		}

		//write to config file
		configFile, err := util.GetConfigFile()
		if err != nil {
			util.HandleError(err, "[infisical user switch]: Unable to get config file")
		}

		configFile.LoggedInUserEmail = profile

		//set logged in user domain
		ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile)

		if !ok {
			//profile not in loggedInUsers
			configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{
				Email:  profile,
				Domain: config.INFISICAL_URL,
			})
			//set logged in user domain
			configFile.LoggedInUserDomain = config.INFISICAL_URL

		} else {
			//exists, set logged in user domain
			for _, v := range configFile.LoggedInUsers {
				if profile == v.Email {
					configFile.LoggedInUserDomain = v.Domain
					break
				}
			}
		}

		err = util.WriteConfigFile(&configFile)
		if err != nil {
			util.HandleError(err, "")
		}

		Telemetry.CaptureEvent("cli-command:user switch", posthog.NewProperties().Set("numberOfLoggedInProfiles", len(loggedInProfiles)).Set("version", util.CLI_VERSION))
	},
}

var updateCmd = &cobra.Command{
	Use:                   "update",
	Short:                 "Used to update properties of an Infisical profile",
	DisableFlagsInUseLine: true,
	Example:               "infisical user update",
	Args:                  cobra.ExactArgs(0),
	Run: func(cmd *cobra.Command, args []string) {
		cmd.Help()
	},
}

var domainCmd = &cobra.Command{
	Use:                   "domain",
	Short:                 "Used to update the domain of an Infisical profile",
	DisableFlagsInUseLine: true,
	Example:               "infisical user update domain",
	Args:                  cobra.ExactArgs(0),
	PreRun: func(cmd *cobra.Command, args []string) {
		util.RequireLogin()
	},
	Run: func(cmd *cobra.Command, args []string) {
		//prompt for profiles selection
		loggedInProfiles, err := getLoggedInUsers()
		if err != nil {
			util.HandleError(err, "[infisical user update domain]: Unable to get logged Profiles")
		}

		//prompt user
		profile, err := LoggedInUsersPrompt(loggedInProfiles)
		if err != nil {
			util.HandleError(err, "[infisical user update domain]: Prompt error")
		}

		domain := ""
		domainQuery := true
		if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {

			override, err := DomainOverridePrompt()
			if err != nil {
				util.HandleError(err, "[infisical user update domain]: Domain override prompt error")
			}

			if !override {
				domainQuery = false
				domain = config.INFISICAL_URL_MANUAL_OVERRIDE
			}

		}

		if domainQuery {
			//prompt to update domain
			domain, err = NewDomainPrompt()
			if err != nil {
				util.HandleError(err, "[infisical user update domain]: Prompt error")
			}
		}

		//write to config file
		configFile, err := util.GetConfigFile()
		if err != nil {
			util.HandleError(err, "[infisical user update domain]: Unable to get config file")
		}

		//check if profile in logged in profiles

		//if not add new profile loggedInUsers
		//else update profile from loggedinUsers slice
		ok := util.ConfigContainsEmail(configFile.LoggedInUsers, profile)
		if !ok {
			configFile.LoggedInUsers = append(configFile.LoggedInUsers, models.LoggedInUser{
				Email:  profile,
				Domain: domain,
			})
		} else {
			//exists, set logged in user domain
			for idx, v := range configFile.LoggedInUsers {
				if profile == v.Email {
					configFile.LoggedInUsers[idx].Domain = domain //inplace
					break
				}
			}

		}
		//check if current loggedinuser is selected profile
		//if yes set current domain to changed domain
		if configFile.LoggedInUserEmail == profile {
			configFile.LoggedInUserDomain = domain
		}

		err = util.WriteConfigFile(&configFile)
		if err != nil {
			util.HandleError(err, "")
		}
		Telemetry.CaptureEvent("cli-command:user domain", posthog.NewProperties().Set("version", util.CLI_VERSION))
	},
}

func init() {
	updateCmd.AddCommand(domainCmd)
	userCmd.AddCommand(updateCmd)
	userCmd.AddCommand(switchCmd)
	rootCmd.AddCommand(userCmd)
}

// This returns all logged in user emails from the config file.
// If none, it returns the current logged in user in a slice
func getLoggedInUsers() ([]string, error) {
	loggedInProfiles := []string{}

	if util.ConfigFileExists() {
		configFile, err := util.GetConfigFile()
		if err != nil {
			return loggedInProfiles, err
		}

		//get logged in profiles
		//
		if len(configFile.LoggedInUsers) > 0 {
			for _, v := range configFile.LoggedInUsers {
				loggedInProfiles = append(loggedInProfiles, v.Email)
			}
		} else {

			loggedInProfiles = append(loggedInProfiles, configFile.LoggedInUserEmail)
		}
		return loggedInProfiles, nil
	} else {
		//empty
		return loggedInProfiles, errors.New("couldn't retrieve config file")
	}
}

func NewDomainPrompt() (string, error) {
	urlValidation := func(input string) error {
		_, err := url.ParseRequestURI(input)
		if err != nil {
			return errors.New("this is an invalid url")
		}
		return nil
	}

	//else run prompt to enter domain
	domainPrompt := promptui.Prompt{
		Label:    "New Domain",
		Validate: urlValidation,
		Default:  "Example - https://my-self-hosted-instance.com/api",
	}

	domain, err := domainPrompt.Run()
	if err != nil {
		return "", err
	}

	return domain, nil
}

func LoggedInUsersPrompt(profiles []string) (string, error) {
	prompt := promptui.Select{Label: "Which of your Infisical profiles would you like to use",
		Items: profiles,
		Size:  7,
	}

	idx, _, err := prompt.Run()
	if err != nil {
		return "", err
	}

	return profiles[idx], nil
}
