package login

import (
	"fmt"
	"html/template"
	"net/http"

	"github.com/golang/glog"

	"k8s.io/kubernetes/pkg/auth/user"
	utilruntime "k8s.io/kubernetes/pkg/util/runtime"

	"github.com/openshift/origin/pkg/auth/authenticator"
	"github.com/openshift/origin/pkg/auth/oauth/handlers"
	"github.com/openshift/origin/pkg/auth/server/csrf"
)

type RequestAuthenticator interface {
	authenticator.Request
	handlers.AuthenticationSuccessHandler
}

type ConfirmFormRenderer interface {
	Render(form ConfirmForm, w http.ResponseWriter, req *http.Request)
}

type ConfirmForm struct {
	Action string
	Error  string
	User   user.Info
	Values ConfirmFormValues
}

type ConfirmFormValues struct {
	Then string
	CSRF string
}

type Confirm struct {
	csrf   csrf.CSRF
	auth   RequestAuthenticator
	render ConfirmFormRenderer
}

func NewConfirm(csrf csrf.CSRF, auth RequestAuthenticator, render ConfirmFormRenderer) *Confirm {
	return &Confirm{
		csrf:   csrf,
		auth:   auth,
		render: render,
	}
}

func (c *Confirm) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "GET":
		c.handleConfirmForm(w, req)
	case "POST":
		c.handleConfirm(w, req)
	default:
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
	}
}

func (c *Confirm) handleConfirmForm(w http.ResponseWriter, req *http.Request) {
	uri, err := getBaseURL(req)
	if err != nil {
		glog.Errorf("Unable to generate base URL: %v", err)
		http.Error(w, "Unable to determine URL", http.StatusInternalServerError)
		return
	}

	form := ConfirmForm{
		Action: uri.String(),
	}
	if then := req.URL.Query().Get("then"); then != "" {
		// TODO: sanitize 'then'
		form.Values.Then = then
	}
	switch req.URL.Query().Get("reason") {
	case "":
		break
	default:
		form.Error = "An unknown error has occurred. Please try again."
	}

	csrf, err := c.csrf.Generate(w, req)
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("unable to generate CSRF token: %v", err))
	}
	form.Values.CSRF = csrf

	user, ok, err := c.auth.AuthenticateRequest(req)
	if err != nil || !ok {
		glog.Errorf("Unable to authenticate request: %v", err)
		form.Error = "An unknown error has occurred. Contact your administrator."
		c.render.Render(form, w, req)
		return
	}

	form.User = user

	c.render.Render(form, w, req)
}

func (c *Confirm) handleConfirm(w http.ResponseWriter, req *http.Request) {
	if ok, err := c.csrf.Check(req, req.FormValue("csrf")); !ok || err != nil {
		glog.Errorf("Unable to check CSRF token: %v", err)
		failed("token expired", w, req)
		return
	}

	user, ok, err := c.auth.AuthenticateRequest(req)
	if err != nil || !ok {
		if err != nil {
			glog.Errorf("Unable authenticate request: %v", err)
		}
		failed("access denied", w, req)
		return
	}

	then := req.FormValue("then")

	c.auth.AuthenticationSucceeded(user, then, w, req)
}

var DefaultConfirmFormRenderer = confirmTemplateRenderer{}

type confirmTemplateRenderer struct{}

func (r confirmTemplateRenderer) Render(form ConfirmForm, w http.ResponseWriter, req *http.Request) {
	w.Header().Add("Content-Type", "text/html; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	if err := confirmTemplate.Execute(w, form); err != nil {
		utilruntime.HandleError(fmt.Errorf("unable render confirm template: %v", err))
	}
}

var confirmTemplate = template.Must(template.New("ConfirmForm").Parse(`
{{ if .Error }}<div class="message">{{ .Error }}</div>{{ end }}
<form action="{{ .Action }}" method="POST">
  <input type="hidden" name="then" value="{{ .Values.Then }}">
  <input type="hidden" name="csrf" value="{{ .Values.CSRF }}">
  {{ if .User }}
  <p>You are now logged in as <strong>{{ .User.GetName }}</strong></p>
  <input type="submit" name="Continue">
  {{ else }}
  <p>You are not currently logged in.</p>
  <input type="submit" disabled="disabled" name="Continue">
  {{ end }}
</form>
`))