package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path"

	"github.com/coreos/dex/client"
	phttp "github.com/coreos/dex/pkg/http"
	"github.com/coreos/dex/pkg/log"
	schema "github.com/coreos/dex/schema/workerschema"
	"github.com/coreos/go-oidc/oidc"
)

type clientResource struct {
	repo client.ClientIdentityRepo
}

func registerClientResource(prefix string, repo client.ClientIdentityRepo) (string, http.Handler) {
	mux := http.NewServeMux()
	c := &clientResource{
		repo: repo,
	}
	relPath := "clients"
	absPath := path.Join(prefix, relPath)
	mux.Handle(absPath, c)
	return relPath, mux
}

func (c *clientResource) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		c.list(w, r)
	case "POST":
		c.create(w, r)
	default:
		msg := fmt.Sprintf("HTTP %s method not supported for this resource", r.Method)
		writeAPIError(w, http.StatusMethodNotAllowed, newAPIError(errorInvalidRequest, msg))
	}
}

func (c *clientResource) list(w http.ResponseWriter, r *http.Request) {
	cs, err := c.repo.All()
	if err != nil {
		writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "error listing clients"))
		return
	}

	scs := make([]*schema.Client, len(cs))
	for i, ci := range cs {
		sc := schema.MapClientIdentityToSchemaClient(ci)
		scs[i] = &sc
	}

	page := schema.ClientPage{
		Clients: scs,
	}
	writeResponseWithBody(w, http.StatusOK, page)
}

func (c *clientResource) create(w http.ResponseWriter, r *http.Request) {
	ct := r.Header.Get("content-type")
	if ct != "application/json" {
		log.Debugf("Unsupported request content-type: %v", ct)
		writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "unsupported content-type"))
		return
	}

	var sc schema.Client
	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&sc)
	if err != nil {
		log.Debugf("Error decoding request body: %v", err)
		writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "unable to decode request body"))
		return
	}

	ci, err := schema.MapSchemaClientToClientIdentity(sc)
	if err != nil {
		log.Debugf("Invalid request data: %v", err)
		writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidClientMetadata, "missing or invalid field: redirectURIs"))
		return
	}

	if err := ci.Metadata.Valid(); err != nil {
		log.Debugf("ClientMetadata invalid: %v", err)
		writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidClientMetadata, err.Error()))
		return
	}

	clientID, err := oidc.GenClientID(ci.Metadata.RedirectURLs[0].Host)
	if err != nil {
		log.Errorf("Failed generating ID for new client: %v", err)
		writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "unable to generate client ID"))
		return
	}

	creds, err := c.repo.New(clientID, ci.Metadata)
	if err != nil {
		log.Errorf("Failed creating client: %v", err)
		writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "unable to create client"))
		return
	}
	ci.Credentials = *creds

	ssc := schema.MapClientIdentityToSchemaClientWithSecret(ci)
	w.Header().Add("Location", phttp.NewResourceLocation(r.URL, ci.Credentials.ID))
	writeResponseWithBody(w, http.StatusCreated, ssc)
}