package basicauthpassword

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/golang/glog"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
	authapi "github.com/openshift/origin/pkg/auth/api"
	"github.com/openshift/origin/pkg/auth/authenticator"
)

// Authenticator uses basic auth to make a request to a JSON-returning URL.
// A 401 status indicate failed auth.
// A non-200 status or the presence of an "error" key with a non-empty
//   value indicates an error:
//   {"error":"Error message"}
// A 200 status with an "id" key indicates success:
//   {"id":"userid"}
// A successful response may also include name and/or email:
//   {"id":"userid", "name": "User Name", "email":"user@example.com"}
type Authenticator struct {
	url    string
	mapper authapi.UserIdentityMapper
}

// RemoteUserData holds user data returned from a remote basic-auth protected endpoint.
// These field names can not be changed unless external integrators are also updated.
type RemoteUserData struct {
	ID    string
	Name  string
	Email string
}

// RemoteError holds error data returned from a remote authentication request
type RemoteError struct {
	Error string
}

// New returns an authenticator which will make a basic auth call to the given url.
func New(url string, mapper authapi.UserIdentityMapper) authenticator.Password {
	return &Authenticator{url, mapper}
}

func (a *Authenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) {
	req, err := http.NewRequest("GET", a.url, nil)
	if err != nil {
		return nil, false, err
	}

	req.SetBasicAuth(username, password)
	req.Header.Set("Accept", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, false, err
	}

	if resp.StatusCode == http.StatusUnauthorized {
		return nil, false, nil
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, false, err
	}

	remoteError := RemoteError{}
	json.Unmarshal(body, &remoteError)
	if remoteError.Error != "" {
		return nil, false, errors.New(remoteError.Error)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, false, fmt.Errorf("An error occurred while authenticating (%d)", resp.StatusCode)
	}

	remoteUserData := RemoteUserData{}
	err = json.Unmarshal(body, &remoteUserData)
	if err != nil {
		return nil, false, err
	}

	if len(remoteUserData.ID) == 0 {
		return nil, false, errors.New("Could not retrieve user data")
	}

	identity := &authapi.DefaultUserIdentityInfo{
		UserName: username,
		Extra: map[string]string{
			"name":  remoteUserData.Name,
			"email": remoteUserData.Email,
		},
	}
	user, err := a.mapper.UserFor(identity)
	glog.V(4).Infof("Got userIdentityMapping: %#v", user)
	if err != nil {
		return nil, false, fmt.Errorf("Error creating or updating mapping for: %#v due to %v", identity, err)
	}

	return user, true, nil
}