package http

import (
	"bytes"
	"html/template"
	"log"

	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/config/flags"
	"github.com/spf13/pflag"
)

// AuthHelp returns text describing the http authentication to add to the command help.
func AuthHelp(prefix string) string {
	help := `#### Authentication

By default this will serve files without needing a login.

You can either use an htpasswd file which can take lots of users, or
set a single username and password with the ` + "`--{{ .Prefix }}user` and `--{{ .Prefix }}pass`" + ` flags.

If no static users are configured by either of the above methods, and client
certificates are required by the ` + "`--client-ca`" + ` flag passed to the server, the
client certificate common name will be considered as the username.

Use ` + "`--{{ .Prefix }}htpasswd /path/to/htpasswd`" + ` to provide an htpasswd file.  This is
in standard apache format and supports MD5, SHA1 and BCrypt for basic
authentication.  Bcrypt is recommended.

To create an htpasswd file:

    touch htpasswd
    htpasswd -B htpasswd user
    htpasswd -B htpasswd anotherUser

The password file can be updated while rclone is running.

Use ` + "`--{{ .Prefix }}realm`" + ` to set the authentication realm.

Use ` + "`--{{ .Prefix }}salt`" + ` to change the password hashing salt from the default.

`
	tmpl, err := template.New("auth help").Parse(help)
	if err != nil {
		log.Fatal("Fatal error parsing template", err)
	}

	data := struct {
		Prefix string
	}{
		Prefix: prefix,
	}
	buf := &bytes.Buffer{}
	err = tmpl.Execute(buf, data)
	if err != nil {
		log.Fatal("Fatal error executing template", err)
	}
	return buf.String()
}

// CustomAuthFn if used will be used to authenticate user, pass. If an error
// is returned then the user is not authenticated.
//
// If a non nil value is returned then it is added to the context under the key
type CustomAuthFn func(user, pass string) (value interface{}, err error)

// AuthConfigInfo descripts the Options in use
var AuthConfigInfo = fs.Options{{
	Name:    "htpasswd",
	Default: "",
	Help:    "A htpasswd file - if not provided no authentication is done",
}, {
	Name:    "realm",
	Default: "",
	Help:    "Realm for authentication",
}, {
	Name:    "user",
	Default: "",
	Help:    "User name for authentication",
}, {
	Name:    "pass",
	Default: "",
	Help:    "Password for authentication",
}, {
	Name:    "salt",
	Default: "dlPL2MqE",
	Help:    "Password hashing salt",
}}

// AuthConfig contains options for the http authentication
type AuthConfig struct {
	HtPasswd     string       `config:"htpasswd"`   // htpasswd file - if not provided no authentication is done
	Realm        string       `config:"realm"`      // realm for authentication
	BasicUser    string       `config:"user"`       // single username for basic auth if not using Htpasswd
	BasicPass    string       `config:"pass"`       // password for BasicUser
	Salt         string       `config:"salt"`       // password hashing salt
	CustomAuthFn CustomAuthFn `json:"-" config:"-"` // custom Auth (not set by command line flags)
}

// AddFlagsPrefix adds flags to the flag set for AuthConfig
func (cfg *AuthConfig) AddFlagsPrefix(flagSet *pflag.FlagSet, prefix string) {
	flags.StringVarP(flagSet, &cfg.HtPasswd, prefix+"htpasswd", "", cfg.HtPasswd, "A htpasswd file - if not provided no authentication is done", prefix)
	flags.StringVarP(flagSet, &cfg.Realm, prefix+"realm", "", cfg.Realm, "Realm for authentication", prefix)
	flags.StringVarP(flagSet, &cfg.BasicUser, prefix+"user", "", cfg.BasicUser, "User name for authentication", prefix)
	flags.StringVarP(flagSet, &cfg.BasicPass, prefix+"pass", "", cfg.BasicPass, "Password for authentication", prefix)
	flags.StringVarP(flagSet, &cfg.Salt, prefix+"salt", "", cfg.Salt, "Password hashing salt", prefix)
}

// AddAuthFlagsPrefix adds flags to the flag set for AuthConfig
func AddAuthFlagsPrefix(flagSet *pflag.FlagSet, prefix string, cfg *AuthConfig) {
	cfg.AddFlagsPrefix(flagSet, prefix)
}

// DefaultAuthCfg returns a new config which can be customized by command line flags
//
// Note that this needs to be kept in sync with AuthConfigInfo above and
// can be removed when all callers have been converted.
func DefaultAuthCfg() AuthConfig {
	return AuthConfig{
		Salt: "dlPL2MqE",
	}
}
