package integration

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"

	"github.com/golang/glog"
	"github.com/gorilla/context"

	knet "k8s.io/apimachinery/pkg/util/net"
)

type User struct {
	ID       string
	Password string
	Name     string
	Email    string
}

type BasicAuthChallenger struct {
	realm                string
	users                map[string]User
	authenticatedHandler http.Handler
}

// NewBasicAuthChallenger provides a simple basic auth server that is compatible with our basic auth password validator
func NewBasicAuthChallenger(realm string, users []User, authenticatedHandler http.Handler) BasicAuthChallenger {
	userMap := make(map[string]User, len(users))
	for _, user := range users {
		userMap[user.ID] = user
	}

	return BasicAuthChallenger{realm, userMap, authenticatedHandler}
}

func (challenger BasicAuthChallenger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	glog.Infof("--- %v\n", r.URL)

	authHeader := r.Header["Authorization"]
	if len(authHeader) == 0 {
		challenger.Challenge(w)
		return
	}

	auth := strings.SplitN(authHeader[0], " ", 2)
	if len(auth) != 2 || auth[0] != "Basic" {
		Error("bad syntax", http.StatusBadRequest, w)
		return
	}

	payload, err := base64.StdEncoding.DecodeString(auth[1])
	if err != nil {
		Error("bad syntax", http.StatusBadRequest, w)
		return
	}

	pair := strings.SplitN(string(payload), ":", 2)
	if len(pair) != 2 {
		Error("bad syntax", http.StatusBadRequest, w)
		return
	}

	if !challenger.Validate(pair[0], pair[1]) {
		challenger.Challenge(w)
		return
	}

	context.Set(r, "username", challenger.users[pair[0]])

	challenger.authenticatedHandler.ServeHTTP(w, r)
}

func (challenger *BasicAuthChallenger) Challenge(w http.ResponseWriter) {
	glog.Infof("Sending challenge\n")

	w.Header().Set("WWW-Authenticate", "Basic realm=\""+challenger.realm+"\"")
	Error("Authorization failed", http.StatusUnauthorized, w)
}

func Error(message string, status int, w http.ResponseWriter) {
	glog.Infof("Writing error: %s\n", message)

	data := map[string]string{"error": message}
	json, _ := json.Marshal(data)

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	io.WriteString(w, string(json))
}

func (challenger *BasicAuthChallenger) Validate(username, password string) bool {
	knownUser, exists := challenger.users[username]
	if !exists {
		return false
	}

	if knownUser.Password == password {
		glog.Infof("Validated user: %s\n", username)
		return true
	}

	glog.Infof("Rejected user: %s\n", username)
	return false
}

type identifyingHandler struct {
}

func (handler *identifyingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	user := context.Get(r, "username")
	if user == nil {
		Error("No user found", http.StatusBadRequest, w)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	result, _ := json.Marshal(user)
	io.WriteString(w, string(result))
}

func NewIdentifyingHandler() http.Handler {
	return &identifyingHandler{}
}

type xRemoteUserProxyingHandler struct {
	proxier *httputil.ReverseProxy
}

func (handler *xRemoteUserProxyingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	user := context.Get(r, "username")
	if user == nil {
		Error("No user found", http.StatusBadRequest, w)
		return
	}

	r.Header.Add("X-Remote-User", user.(User).ID)
	handler.proxier.ServeHTTP(w, r)
}

func NewXRemoteUserProxyingHandler(rawURL string) http.Handler {
	parsedURL, _ := url.Parse(rawURL)
	proxier := httputil.NewSingleHostReverseProxy(parsedURL)
	proxier.Transport = insecureTransport()

	// proxier.Transport = NewBasicAuthRoundTripper(http.DefaultTransport)
	return &xRemoteUserProxyingHandler{proxier}
}

func insecureTransport() *http.Transport {
	return knet.SetTransportDefaults(&http.Transport{
		TLSClientConfig: &tls.Config{
			// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
			MinVersion:         tls.VersionTLS10,
			InsecureSkipVerify: true,
		},
	})
}
