package cmd

import (
	"fmt"
	"io"
	"sort"

	"k8s.io/kubernetes/pkg/client/restclient"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	kclientcmd "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
	clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"

	"github.com/openshift/origin/pkg/client"
	cliconfig "github.com/openshift/origin/pkg/cmd/cli/config"
	"github.com/openshift/origin/pkg/cmd/templates"
	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
	"github.com/openshift/origin/pkg/project/api"

	"github.com/spf13/cobra"
)

type ProjectsOptions struct {
	Config       clientcmdapi.Config
	ClientConfig *restclient.Config
	Client       *client.Client
	KubeClient   kclient.Interface
	Out          io.Writer
	PathOptions  *kclientcmd.PathOptions

	// internal strings
	CommandName string

	DisplayShort bool
}

// SortByProjectName is sort
type SortByProjectName []api.Project

func (p SortByProjectName) Len() int {
	return len(p)
}
func (p SortByProjectName) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}
func (p SortByProjectName) Less(i, j int) bool {
	return p[i].Name < p[j].Name
}

var (
	projectsLong = templates.LongDesc(`
		Display information about the current active project and existing projects on the server.

		For advanced configuration, or to manage the contents of your config file, use the 'config'
		command.`)
)

// NewCmdProjects implements the OpenShift cli rollback command
func NewCmdProjects(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
	options := &ProjectsOptions{}

	cmd := &cobra.Command{
		Use:   "projects",
		Short: "Display existing projects",
		Long:  projectsLong,
		Run: func(cmd *cobra.Command, args []string) {
			options.PathOptions = cliconfig.NewPathOptions(cmd)

			if err := options.Complete(f, args, fullName, out); err != nil {
				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
			}

			if err := options.RunProjects(); err != nil {
				kcmdutil.CheckErr(err)
			}
		},
	}

	cmd.Flags().BoolVarP(&options.DisplayShort, "short", "q", false, "If true, display only the project names")
	return cmd
}

func (o *ProjectsOptions) Complete(f *clientcmd.Factory, args []string, commandName string, out io.Writer) error {
	if len(args) > 0 {
		return fmt.Errorf("no arguments should be passed")
	}

	o.CommandName = commandName

	var err error
	o.Config, err = f.OpenShiftClientConfig.RawConfig()
	if err != nil {
		return err
	}

	o.ClientConfig, err = f.OpenShiftClientConfig.ClientConfig()
	if err != nil {
		return err
	}

	o.Client, o.KubeClient, err = f.Clients()
	if err != nil {
		return err
	}

	o.Out = out

	return nil
}

// RunProjects lists all projects a user belongs to
func (o ProjectsOptions) RunProjects() error {
	config := o.Config
	clientCfg := o.ClientConfig
	out := o.Out

	var currentProject string
	currentContext := config.Contexts[config.CurrentContext]
	if currentContext != nil {
		currentProject = currentContext.Namespace
	}

	var currentProjectExists bool
	var currentProjectErr error

	client := o.Client

	if len(currentProject) > 0 {
		if currentProjectErr := confirmProjectAccess(currentProject, o.Client, o.KubeClient); currentProjectErr == nil {
			currentProjectExists = true
		}
	}

	var defaultContextName string
	if currentContext != nil {
		defaultContextName = cliconfig.GetContextNickname(currentContext.Namespace, currentContext.Cluster, currentContext.AuthInfo)
	}

	var msg string
	projects, err := getProjects(client, o.KubeClient)
	if err == nil {
		switch len(projects) {
		case 0:
			msg += "You are not a member of any projects. You can request a project to be created with the 'new-project' command."
		case 1:
			if o.DisplayShort {
				msg += fmt.Sprintf("%s", api.DisplayNameAndNameForProject(&projects[0]))
			} else {
				msg += fmt.Sprintf("You have one project on this server: %q.", api.DisplayNameAndNameForProject(&projects[0]))
			}
		default:
			asterisk := ""
			count := 0
			if !o.DisplayShort {
				msg += fmt.Sprintf("You have access to the following projects and can switch between them with '%s project <projectname>':\n", o.CommandName)
			}

			sort.Sort(SortByProjectName(projects))
			for _, project := range projects {
				count = count + 1
				displayName := project.Annotations["openshift.io/display-name"]
				linebreak := "\n"
				if len(displayName) == 0 {
					displayName = project.Annotations["displayName"]
				}

				if currentProjectExists && !o.DisplayShort {
					asterisk = "    "
					if currentProject == project.Name {
						asterisk = "  * "
					}
				}
				if len(displayName) > 0 && displayName != project.Name && !o.DisplayShort {
					msg += fmt.Sprintf("\n"+asterisk+"%s - %s", project.Name, displayName)
				} else {
					if o.DisplayShort && count == 1 {
						linebreak = ""
					}
					msg += fmt.Sprintf(linebreak+asterisk+"%s", project.Name)
				}
			}
		}
		fmt.Println(msg)

		if len(projects) > 0 && !o.DisplayShort {
			if !currentProjectExists {
				if clientcmd.IsForbidden(currentProjectErr) {
					fmt.Printf("You do not have rights to view project %q. Please switch to an existing one.\n", currentProject)
				}
				return currentProjectErr
			}

			// if they specified a project name and got a generated context, then only show the information they care about.  They won't recognize
			// a context name they didn't choose
			if config.CurrentContext == defaultContextName {
				fmt.Fprintf(out, "\nUsing project %q on server %q.\n", currentProject, clientCfg.Host)
			} else {
				fmt.Fprintf(out, "\nUsing project %q from context named %q on server %q.\n", currentProject, config.CurrentContext, clientCfg.Host)
			}
		}
		return nil
	}

	return err
}