package auth

import (
	"html/template"
	"net/http"
	"net/url"
	"time"

	"code.secondbit.org/uuid"
)

const (
	getGrantTemplateName   = "get_grant"
	defaultGrantExpiration = 600 // default to ten minute grant expirations
)

// GetGrantHandler presents and processes the page for asking a user to grant access
// to their data. See RFC 6749, Section 4.1.
func GetGrantHandler(w http.ResponseWriter, r *http.Request, context Context) {
	if r.URL.Query().Get("client_id") == "" {
		w.WriteHeader(http.StatusBadRequest)
		context.Render(w, getGrantTemplateName, map[string]interface{}{
			"error": template.HTML("Client ID must be specified in the request."),
		})
		return
	}
	clientID, err := uuid.Parse(r.URL.Query().Get("client_id"))
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		context.Render(w, getGrantTemplateName, map[string]interface{}{
			"error": template.HTML("client_id is not a valid Client ID."),
		})
		return
	}
	redirectURI := r.URL.Query().Get("redirect_uri")
	redirectURL, err := url.Parse(redirectURI)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		context.Render(w, getGrantTemplateName, map[string]interface{}{
			"error": template.HTML("The redirect_uri specified is not valid."),
		})
		return
	}
	client, err := context.GetClient(clientID)
	if err != nil {
		if err == ErrClientNotFound {
			w.WriteHeader(http.StatusBadRequest)
			context.Render(w, getGrantTemplateName, map[string]interface{}{
				"error": template.HTML("The specified Client couldn&rsquo;t be found."),
			})
		} else {
			w.WriteHeader(http.StatusInternalServerError)
			context.Render(w, getGrantTemplateName, map[string]interface{}{
				"internal_error": template.HTML(err.Error()),
			})
		}
		return
	}
	// whether a redirect URI is valid or not depends on the number of endpoints
	// the client has registered
	numEndpoints, err := context.CountEndpoints(clientID)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		context.Render(w, getGrantTemplateName, map[string]interface{}{
			"internal_error": template.HTML(err.Error()),
		})
		return
	}
	var validURI bool
	if redirectURI != "" {
		// BUG(paddy): We really should normalize URIs before trying to compare them.
		validURI, err = context.CheckEndpoint(clientID, redirectURI)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			context.Render(w, getGrantTemplateName, map[string]interface{}{
				"internal_error": template.HTML(err.Error()),
			})
			return
		}
	} else if redirectURI == "" && numEndpoints == 1 {
		// if we don't specify the endpoint and there's only one endpoint, the
		// request is valid, and we're redirecting to that one endpoint
		validURI = true
		endpoints, err := context.ListEndpoints(clientID, 1, 0)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			context.Render(w, getGrantTemplateName, map[string]interface{}{
				"internal_error": template.HTML(err.Error()),
			})
			return
		}
		if len(endpoints) != 1 {
			validURI = false
		} else {
			u := endpoints[0].URI // Copy it here to avoid grabbing a pointer to the memstore
			redirectURI = u.String()
			redirectURL = &u
		}
	} else {
		validURI = false
	}
	if !validURI {
		w.WriteHeader(http.StatusBadRequest)
		context.Render(w, getGrantTemplateName, map[string]interface{}{
			"error": template.HTML("The redirect_uri specified is not valid."),
		})
		return
	}
	scope := r.URL.Query().Get("scope")
	state := r.URL.Query().Get("state")
	if r.URL.Query().Get("response_type") != "code" {
		q := redirectURL.Query()
		q.Add("error", "invalid_request")
		q.Add("state", state)
		redirectURL.RawQuery = q.Encode()
		http.Redirect(w, r, redirectURL.String(), http.StatusFound)
		return
	}
	if r.Method == "POST" {
		// BUG(paddy): We need to implement CSRF protection when obtaining a grant code.
		if r.PostFormValue("grant") == "approved" {
			code := uuid.NewID().String()
			grant := Grant{
				Code:        code,
				Created:     time.Now(),
				ExpiresIn:   defaultGrantExpiration,
				ClientID:    clientID,
				Scope:       scope,
				RedirectURI: redirectURI,
				State:       state,
			}
			err := context.SaveGrant(grant)
			if err != nil {
				q := redirectURL.Query()
				q.Add("error", "server_error")
				q.Add("state", state)
				redirectURL.RawQuery = q.Encode()
				http.Redirect(w, r, redirectURL.String(), http.StatusFound)
				return
			}
			q := redirectURL.Query()
			q.Add("code", code)
			q.Add("state", state)
			redirectURL.RawQuery = q.Encode()
			http.Redirect(w, r, redirectURL.String(), http.StatusFound)
			return
		}
		q := redirectURL.Query()
		q.Add("error", "access_denied")
		q.Add("state", state)
		redirectURL.RawQuery = q.Encode()
		http.Redirect(w, r, redirectURL.String(), http.StatusFound)
		return
	}
	w.WriteHeader(http.StatusOK)
	context.Render(w, getGrantTemplateName, map[string]interface{}{
		"client": client,
	})
}
