package client

import (
	"fmt"
	"io/ioutil"
	"os"

	flag "github.com/spf13/pflag"
	"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"

	"github.com/openshift/origin/pkg/cmd/cli/config"
	"github.com/openshift/origin/pkg/diagnostics/types"
	"github.com/openshift/origin/pkg/diagnostics/util"
)

// ConfigLoading is a little special in that it is run separately as a precondition
// in order to determine whether we can run other dependent diagnostics.
type ConfigLoading struct {
	ConfFlagName   string
	ClientFlags    *flag.FlagSet
	successfulLoad bool // set if at least one file loaded
}

// Name is part of the Diagnostic interface and just returns name.
func (d *ConfigLoading) Name() string {
	return "ConfigLoading"
}

// Description is part of the Diagnostic interface and provides a user-focused description of what the diagnostic does.
func (d *ConfigLoading) Description() string {
	return "Try to load client config file(s) and report what happens"
}

// CanRun is part of the Diagnostic interface; it determines if the conditions are right to run this diagnostic.
func (d *ConfigLoading) CanRun() (bool, error) {
	return true, nil
}

// SuccessfulLoad returns whether the client config was found
func (d *ConfigLoading) SuccessfulLoad() bool {
	return d.successfulLoad
}

// Check is part of the Diagnostic interface; it runs the actual diagnostic logic
func (d *ConfigLoading) Check() types.DiagnosticResult {
	r := types.NewDiagnosticResult("ConfigLoading")
	confFlagValue := d.ClientFlags.Lookup(d.ConfFlagName).Value.String()

	var foundPath string
	rules := config.NewOpenShiftClientConfigLoadingRules()
	paths := append([]string{confFlagValue}, rules.Precedence...)
	for index, path := range paths {
		errmsg := ""
		switch index {
		case 0:
			errmsg = fmt.Sprintf("--%s specified that client config should be at %s\n", d.ConfFlagName, path)
		case len(paths) - 1: // config in ~/.kube
		// no error message indicated if it is not there... user didn't say it would be
		default: // can be multiple paths from the env var in theory; all cases should go here
			if len(os.Getenv(config.OpenShiftConfigPathEnvVar)) != 0 {
				errmsg = fmt.Sprintf("Env var %s specified that client config could be at %s\n", config.OpenShiftConfigPathEnvVar, path)
			}
		}

		if d.canOpenConfigFile(path, errmsg, r) && foundPath == "" {
			d.successfulLoad = true
			foundPath = path
		}
	}
	if foundPath != "" {
		if confFlagValue != "" && confFlagValue != foundPath {
			// found config but not where --config said
			r.Error("DCli1001", nil, fmt.Sprintf(`
The client configuration file was not found where the --%s flag indicated:
  %s
A config file was found at the following location:
  %s
If you wish to use this file for client configuration, you can specify it
with the --%[1]s flag, or just not specify the flag.
			`, d.ConfFlagName, confFlagValue, foundPath))
		}
	} else { // not found, check for master-generated ones to recommend
		if confFlagValue != "" {
			r.Error("DCli1002", nil, fmt.Sprintf("Did not find config file where --%s=%s indicated", d.ConfFlagName, confFlagValue))
		}
		adminWarningF := `
No client config file was available; however, one exists at
    %[2]s
which may have been generated automatically by the master.
If you want to use this config, you should copy it to the
standard location (%[3]s),
or you can set the environment variable %[1]s:
    export %[1]s=%[2]s
If not, obtain a config file and place it in the standard
location for use by the client and diagnostics.
`
		// look for it in auto-generated locations when not found properly
		for _, path := range util.AdminKubeConfigPaths {
			msg := fmt.Sprintf("Looking for a possible client config at %s\n", path)
			if d.canOpenConfigFile(path, msg, r) {
				r.Warn("DCli1003", nil, fmt.Sprintf(adminWarningF, config.OpenShiftConfigPathEnvVar, path, config.RecommendedHomeFile))
				break
			}
		}
	}
	return r
}

// ----------------------------------------------------------
// Attempt to open file at path as client config
// If there is a problem and errmsg is set, log an error
func (d ConfigLoading) canOpenConfigFile(path string, errmsg string, r types.DiagnosticResult) bool {
	var file *os.File
	var err error
	if path == "" { // empty param/envvar
		return false
	} else if file, err = os.Open(path); err == nil {
		r.Debug("DCli1004", fmt.Sprintf("Reading client config at %s", path))
	} else if errmsg == "" {
		r.Debug("DCli1005", fmt.Sprintf("Could not read client config at %s:\n%#v", path, err))
	} else if os.IsNotExist(err) {
		r.Debug("DCli1006", errmsg+"but that file does not exist.")
	} else if os.IsPermission(err) {
		r.Error("DCli1007", err, errmsg+"but lack permission to read that file.")
	} else {
		r.Error("DCli1008", err, fmt.Sprintf("%sbut there was an error opening it:\n%#v", errmsg, err))
	}
	if file != nil { // it is open for reading
		defer file.Close()
		if buffer, err := ioutil.ReadAll(file); err != nil {
			r.Error("DCli1009", err, fmt.Sprintf("Unexpected error while reading client config file (%s): %v", path, err))
		} else if _, err := clientcmd.Load(buffer); err != nil {
			r.Error("DCli1010", err, fmt.Sprintf(`
Error reading YAML from client config file (%s):
  %v
This file may have been truncated or mis-edited.
Please fix, remove, or obtain a new client config`, file.Name(), err))
		} else {
			r.Info("DCli1011", fmt.Sprintf("Successfully read a client config file at '%s'", path))
			/* Note, we're not going to use this config file directly.
			 * Instead, we'll defer to the openshift client code to assimilate
			 * flags, env vars, and the potential hierarchy of config files
			 * into an actual configuration that the client uses.
			 * However, for diagnostic purposes, record the files we find.
			 */
			return true
		}
	}
	return false
}