// Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package debugstatus

import (
	"net/http"

	"gopkg.in/errgo.v1"

	pprof "github.com/juju/httpprof"
	"github.com/juju/httprequest"
)

// Version describes the current version of the code being run.
type Version struct {
	GitCommit string
	Version   string
}

// Handler implements a type that can be used with httprequest.Handlers
// to serve a standard set of /debug endpoints, including
// the version of the system, its current health status
// the runtime profiling information.
type Handler struct {
	// Check will be called to obtain the current health of the
	// system. It should return a map as returned from the
	// Check function. If this is nil, an empty result will
	// always be returned from /debug/status.
	Check func() map[string]CheckResult

	// Version should hold the current version
	// of the binary running the server, served
	// from the /debug/info endpoint.
	Version Version

	// CheckPprofAllowed will be used to check whether the
	// given pprof request should be allowed.
	// It should return an error if not, which will not be masked.
	// If this is nil, no access will be allowed to any
	// of the endpoints under /debug/pprof - the
	// error returned will be ErrNoPprofConfigured.
	CheckPprofAllowed func(req *http.Request) error
}

// DebugStatusRequest describes the /debug/status endpoint.
type DebugStatusRequest struct {
	httprequest.Route `httprequest:"GET /debug/status"`
}

// DebugStatus returns the current status of the server.
func (h *Handler) DebugStatus(*DebugStatusRequest) (map[string]CheckResult, error) {
	if h.Check == nil {
		return map[string]CheckResult{}, nil
	}
	return h.Check(), nil
}

// DebugInfoRequest describes the /debug/info endpoint.
type DebugInfoRequest struct {
	httprequest.Route `httprequest:"GET /debug/info"`
}

// DebugInfo returns version information on the current server.
func (h *Handler) DebugInfo(*DebugInfoRequest) (Version, error) {
	return h.Version, nil
}

// DebugPprofRequest describes the /debug/pprof/ endpoint.
type DebugPprofRequest struct {
	httprequest.Route `httprequest:"GET /debug/pprof/"`
}

// DebugPprof serves index information on the available pprof endpoints.
func (h *Handler) DebugPprof(p httprequest.Params, _ *DebugPprofRequest) error {
	if err := h.checkPprofAllowed(p.Request); err != nil {
		return err
	}
	pprof.Index(p.Response, p.Request)
	return nil
}

// DebugPprofEndpointsRequest describes the endpoints under /debug/prof.
type DebugPprofEndpointsRequest struct {
	httprequest.Route `httprequest:"GET /debug/pprof/:name"`
	Name              string `httprequest:"name,path"`
}

// DebugPprofEndpoints serves all the endpoints under DebugPprof.
func (h *Handler) DebugPprofEndpoints(p httprequest.Params, r *DebugPprofEndpointsRequest) error {
	if err := h.checkPprofAllowed(p.Request); err != nil {
		return err
	}
	switch r.Name {
	case "cmdline":
		pprof.Cmdline(p.Response, p.Request)
	case "profile":
		pprof.Profile(p.Response, p.Request)
	case "symbol":
		pprof.Symbol(p.Response, p.Request)
	default:
		pprof.Handler(r.Name).ServeHTTP(p.Response, p.Request)
	}
	return nil
}

// ErrNoPprofConfigured is the error returned on access
// to endpoints when Handler.CheckPprofAllowed is nil.
var ErrNoPprofConfigured = errgo.New("no pprof access configured")

// checkPprofAllowed is used instead of h.CheckPprofAllowed
// so that we don't panic if that is nil.
func (h *Handler) checkPprofAllowed(req *http.Request) error {
	if h.CheckPprofAllowed == nil {
		return ErrNoPprofConfigured
	}
	return h.CheckPprofAllowed(req)
}
