package api

import (
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"time"

	"github.com/go-chi/chi"
	"github.com/pkg/errors"
	"github.com/smallstep/certificates/acme"
	"github.com/smallstep/certificates/api"
)

// NewOrderRequest represents the body for a NewOrder request.
type NewOrderRequest struct {
	Identifiers []acme.Identifier `json:"identifiers"`
	NotBefore   time.Time         `json:"notBefore,omitempty"`
	NotAfter    time.Time         `json:"notAfter,omitempty"`
}

// Validate validates a new-order request body.
func (n *NewOrderRequest) Validate() error {
	if len(n.Identifiers) == 0 {
		return acme.MalformedErr(errors.Errorf("identifiers list cannot be empty"))
	}
	for _, id := range n.Identifiers {
		if id.Type != "dns" {
			return acme.MalformedErr(errors.Errorf("identifier type unsupported: %s", id.Type))
		}
	}
	return nil
}

// FinalizeRequest captures the body for a Finalize order request.
type FinalizeRequest struct {
	CSR string `json:"csr"`
	csr *x509.CertificateRequest
}

// Validate validates a finalize request body.
func (f *FinalizeRequest) Validate() error {
	var err error
	csrBytes, err := base64.RawURLEncoding.DecodeString(f.CSR)
	if err != nil {
		return acme.MalformedErr(errors.Wrap(err, "error base64url decoding csr"))
	}
	f.csr, err = x509.ParseCertificateRequest(csrBytes)
	if err != nil {
		return acme.MalformedErr(errors.Wrap(err, "unable to parse csr"))
	}
	if err = f.csr.CheckSignature(); err != nil {
		return acme.MalformedErr(errors.Wrap(err, "csr failed signature check"))
	}
	return nil
}

// NewOrder ACME api for creating a new order.
func (h *Handler) NewOrder(w http.ResponseWriter, r *http.Request) {
	prov, err := provisionerFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	acc, err := accountFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	payload, err := payloadFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	var nor NewOrderRequest
	if err := json.Unmarshal(payload.value, &nor); err != nil {
		api.WriteError(w, acme.MalformedErr(errors.Wrap(err,
			"failed to unmarshal new-order request payload")))
		return
	}
	if err := nor.Validate(); err != nil {
		api.WriteError(w, err)
		return
	}

	o, err := h.Auth.NewOrder(prov, acme.OrderOptions{
		AccountID:   acc.GetID(),
		Identifiers: nor.Identifiers,
		NotBefore:   nor.NotBefore,
		NotAfter:    nor.NotAfter,
	})
	if err != nil {
		api.WriteError(w, err)
		return
	}

	w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.GetID()))
	api.JSONStatus(w, o, http.StatusCreated)
	return
}

// GetOrder ACME api for retrieving an order.
func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
	prov, err := provisionerFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	acc, err := accountFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	oid := chi.URLParam(r, "ordID")
	o, err := h.Auth.GetOrder(prov, acc.GetID(), oid)
	if err != nil {
		api.WriteError(w, err)
		return
	}

	w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.GetID()))
	api.JSON(w, o)
	return
}

// FinalizeOrder attemptst to finalize an order and create a certificate.
func (h *Handler) FinalizeOrder(w http.ResponseWriter, r *http.Request) {
	prov, err := provisionerFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	acc, err := accountFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	payload, err := payloadFromContext(r)
	if err != nil {
		api.WriteError(w, err)
		return
	}
	var fr FinalizeRequest
	if err := json.Unmarshal(payload.value, &fr); err != nil {
		api.WriteError(w, acme.MalformedErr(errors.Wrap(err, "failed to unmarshal finalize-order request payload")))
		return
	}
	if err := fr.Validate(); err != nil {
		api.WriteError(w, err)
		return
	}

	oid := chi.URLParam(r, "ordID")
	o, err := h.Auth.FinalizeOrder(prov, acc.GetID(), oid, fr.csr)
	if err != nil {
		api.WriteError(w, err)
		return
	}

	w.Header().Set("Location", h.Auth.GetLink(acme.OrderLink, acme.URLSafeProvisionerName(prov), true, o.ID))
	api.JSON(w, o)
	return
}
