package instance

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"git.sigsum.org/sigsum-lib-go/pkg/types"
	"github.com/golang/glog"
)

// Handler implements the http.Handler interface, and contains a reference
// to a sigsum server instance as well as a function that uses it.
type Handler struct {
	Instance *Instance
	Endpoint types.Endpoint
	Method   string
	Handler  func(context.Context, *Instance, http.ResponseWriter, *http.Request) (int, error)
}

// Path returns a path that should be configured for this handler
func (h Handler) Path() string {
	if len(h.Instance.Prefix) == 0 {
		return h.Endpoint.Path("", "sigsum", "v0")
	}
	return h.Endpoint.Path("", h.Instance.Prefix, "sigsum", "v0")
}

// ServeHTTP is part of the http.Handler interface
func (a Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// export prometheus metrics
	var now time.Time = time.Now()
	var statusCode int
	defer func() {
		rspcnt.Inc(a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode))
		latency.Observe(time.Now().Sub(now).Seconds(), a.Instance.LogID, string(a.Endpoint), fmt.Sprintf("%d", statusCode))
	}()
	reqcnt.Inc(a.Instance.LogID, string(a.Endpoint))

	ctx, cancel := context.WithDeadline(r.Context(), now.Add(a.Instance.Deadline))
	defer cancel()

	if r.Method != a.Method {
		glog.Warningf("%s/%s: got HTTP %s, wanted HTTP %s", a.Instance.Prefix, string(a.Endpoint), r.Method, a.Method)
		http.Error(w, "", http.StatusMethodNotAllowed)
		return
	}

	statusCode, err := a.Handler(ctx, a.Instance, w, r)
	if err != nil {
		glog.Warningf("handler error %s/%s: %v", a.Instance.Prefix, a.Endpoint, err)
		http.Error(w, fmt.Sprintf("Error=%s\n", err.Error()), statusCode)
	}
}

func addLeaf(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
	glog.V(3).Info("handling add-entry request")
	req, err := i.leafRequestFromHTTP(ctx, r)
	if err != nil {
		return http.StatusBadRequest, err
	}
	if err := i.Client.AddLeaf(ctx, req); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func addCosignature(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
	glog.V(3).Info("handling add-cosignature request")
	req, err := i.cosignatureRequestFromHTTP(r)
	if err != nil {
		return http.StatusBadRequest, err
	}
	vk := i.Witnesses[req.KeyHash]
	if err := i.Stateman.AddCosignature(ctx, &vk, &req.Cosignature); err != nil {
		return http.StatusBadRequest, err
	}
	return http.StatusOK, nil
}

func getTreeHeadToCosign(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) {
	glog.V(3).Info("handling get-tree-head-to-sign request")
	sth, err := i.Stateman.ToCosignTreeHead(ctx)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if err := sth.ToASCII(w); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func getTreeHeadCosigned(ctx context.Context, i *Instance, w http.ResponseWriter, _ *http.Request) (int, error) {
	glog.V(3).Info("handling get-tree-head-cosigned request")
	cth, err := i.Stateman.CosignedTreeHead(ctx)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if err := cth.ToASCII(w); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func getConsistencyProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
	glog.V(3).Info("handling get-consistency-proof request")
	req, err := i.consistencyProofRequestFromHTTP(r)
	if err != nil {
		return http.StatusBadRequest, err
	}
	// XXX: check tree size of latest thing we signed?

	proof, err := i.Client.GetConsistencyProof(ctx, req)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if err := proof.ToASCII(w); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func getInclusionProof(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
	glog.V(3).Info("handling get-proof-by-hash request")
	req, err := i.inclusionProofRequestFromHTTP(r)
	if err != nil {
		return http.StatusBadRequest, err
	}
	// XXX: check tree size of latest thing we signed?

	proof, err := i.Client.GetInclusionProof(ctx, req)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	if err := proof.ToASCII(w); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func getLeaves(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
	glog.V(3).Info("handling get-leaves request")
	req, err := i.leavesRequestFromHTTP(r)
	if err != nil {
		return http.StatusBadRequest, err
	}
	// XXX: check tree size of latest thing we signed?

	leaves, err := i.Client.GetLeaves(ctx, req)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	for _, leaf := range *leaves {
		if err := leaf.ToASCII(w); err != nil {
			return http.StatusInternalServerError, err
		}
	}
	return http.StatusOK, nil
}