package cmd

import (
	"context"
	"fmt"
	"os"

	"github.com/fatih/color"

	api "github.com/porter-dev/porter/api/client"
	"github.com/porter-dev/porter/api/types"
	"github.com/porter-dev/porter/cli/cmd/config"
	loginBrowser "github.com/porter-dev/porter/cli/cmd/login"
	"github.com/porter-dev/porter/cli/cmd/utils"
	"github.com/spf13/cobra"
)

var authCmd = &cobra.Command{
	Use:   "auth",
	Short: "Commands for authenticating to a Porter server",
}

var loginCmd = &cobra.Command{
	Use:   "login",
	Short: "Authorizes a user for a given Porter server",
	Run: func(cmd *cobra.Command, args []string) {
		err := login()
		if err != nil {
			color.Red("Error logging in: %s\n", err.Error())
			os.Exit(1)
		}
	},
}

var registerCmd = &cobra.Command{
	Use:   "register",
	Short: "Creates a user for a given Porter server",
	Run: func(cmd *cobra.Command, args []string) {
		err := register()
		if err != nil {
			color.Red("Error registering: %s\n", err.Error())
			os.Exit(1)
		}
	},
}

var logoutCmd = &cobra.Command{
	Use:   "logout",
	Short: "Logs a user out of a given Porter server",
	Run: func(cmd *cobra.Command, args []string) {
		err := checkLoginAndRun(args, logout)
		if err != nil {
			os.Exit(1)
		}
	},
}

var manual bool = false

func init() {
	rootCmd.AddCommand(authCmd)

	authCmd.AddCommand(loginCmd)
	authCmd.AddCommand(registerCmd)
	authCmd.AddCommand(logoutCmd)

	loginCmd.PersistentFlags().BoolVar(
		&manual,
		"manual",
		false,
		"whether to prompt for manual authentication (username/pw)",
	)
}

func login() error {
	client := api.NewClientWithToken(cliConf.Host+"/api", cliConf.Token)

	user, err := client.AuthCheck(context.Background())

	if err == nil {
		// set the token if the user calls login with the --token flag or the PORTER_TOKEN env
		if cliConf.Token != "" {
			cliConf.SetToken(cliConf.Token)
			color.New(color.FgGreen).Println("Successfully logged in!")

			projID, exists, err := api.GetProjectIDFromToken(cliConf.Token)
			if err != nil {
				return err
			}

			// if project ID does not exist for the token, this is a user-issued CLI token, so the project
			// ID should be queried
			if !exists {
				err = setProjectForUser(client, user.ID)

				if err != nil {
					return err
				}
			} else {
				// if the project ID does exist for the token, this is a project-issued token, and
				// the project should be set automatically
				err = cliConf.SetProject(projID)

				if err != nil {
					return err
				}

				err = setProjectCluster(client, projID)

				if err != nil {
					return err
				}
			}
		} else {
			color.Yellow("You are already logged in. If you'd like to log out, run \"porter auth logout\".")
		}

		return nil
	}

	// check for the --manual flag
	if manual {
		return loginManual()
	}

	// log the user in
	token, err := loginBrowser.Login(cliConf.Host)
	if err != nil {
		return err
	}

	// set the token in config
	err = cliConf.SetToken(token)

	if err != nil {
		return err
	}

	client = api.NewClientWithToken(cliConf.Host+"/api", token)

	user, err = client.AuthCheck(context.Background())

	if err != nil {
		color.Red("Invalid token.")
		return err
	}

	color.New(color.FgGreen).Println("Successfully logged in!")

	return setProjectForUser(client, user.ID)
}

func setProjectForUser(client *api.Client, userID uint) error {
	// get a list of projects, and set the current project
	resp, err := client.ListUserProjects(context.Background())
	if err != nil {
		return err
	}

	projects := *resp

	if len(projects) > 0 {
		cliConf.SetProject(projects[0].ID)

		err = setProjectCluster(client, projects[0].ID)

		if err != nil {
			return err
		}
	}

	return nil
}

func loginManual() error {
	client := api.NewClient(cliConf.Host+"/api", "cookie.json")

	var username, pw string

	fmt.Println("Please log in with an email and password:")

	username, err := utils.PromptPlaintext("Email: ")
	if err != nil {
		return err
	}

	pw, err = utils.PromptPassword("Password: ")

	if err != nil {
		return err
	}

	_, err = client.Login(context.Background(), &types.LoginUserRequest{
		Email:    username,
		Password: pw,
	})

	if err != nil {
		return err
	}

	// set the token to empty since this is manual (cookie-based) login
	cliConf.SetToken("")

	color.New(color.FgGreen).Println("Successfully logged in!")

	// get a list of projects, and set the current project
	resp, err := client.ListUserProjects(context.Background())
	if err != nil {
		return err
	}

	projects := *resp

	if len(projects) > 0 {
		cliConf.SetProject(projects[0].ID)

		err = setProjectCluster(client, projects[0].ID)

		if err != nil {
			return err
		}
	}

	return nil
}

func register() error {
	fmt.Println("Please register your admin account with an email and password:")

	username, err := utils.PromptPlaintext("Email: ")
	if err != nil {
		return err
	}

	pw, err := utils.PromptPasswordWithConfirmation()
	if err != nil {
		return err
	}

	client := config.GetAPIClient()

	resp, err := client.CreateUser(context.Background(), &types.CreateUserRequest{
		Email:    username,
		Password: pw,
	})
	if err != nil {
		return err
	}

	color.New(color.FgGreen).Printf("Created user with email %s and id %d\n", username, resp.ID)

	return nil
}

func logout(user *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
	err := client.Logout(context.Background())
	if err != nil {
		return err
	}

	cliConf.SetToken("")

	color.Green("Successfully logged out")

	return nil
}
