package twitchhttp

import (
	"log"
	"net/http"
	"runtime"
	// importing twitchhttp implies intent to host pprof metrics, so we automatically enable it.
	_ "net/http/pprof"
	"time"

	"code.justin.tv/common/chitin"
	"code.justin.tv/common/config"
	"code.justin.tv/common/gometrics"

	"goji.io"
	"goji.io/middleware"
	"goji.io/pat"
	"golang.org/x/net/context"
)

// Server allows simple definition of an HTTP service which meets Twitch requirements for production readiness.
func init() {
	config.Register(map[string]string{
		"bind-address":       ":8000",
		"debug-bind-address": ":6000",
	})
}

// NewServer allocates and returns a Server. The /debug/running endpoint is registered for load balancer health checks.
func NewServer() *goji.Mux {
	debugRouter := goji.SubMux()
	debugRouter.HandleFuncC(pat.Get("/running"), healthcheck)

	router := goji.NewMux()
	router.UseC(RollbarMiddleware)
	router.UseC(SkipMiddleware(debugRouter))
	router.HandleC(pat.New("/debug/*"), debugRouter)

	return router
}

// ListenAndServe starts the application server, as well as a debug server for pprof debugging. Go runtime metrics begin emitting to statsd.
func ListenAndServe(h goji.Handler) error {
	ctx, err := chitin.ExperimentalTraceContext(context.Background())
	if err != nil {
		return err
	}

	go func() {
		log.Printf("debug listening on %v", config.Resolve("debug-bind-address"))
		log.Print(http.ListenAndServe(config.Resolve("debug-bind-address"), nil))
	}()

	gometrics.Monitor(config.Statsd(), time.Second*5)

	log.Printf("application listening on %v", config.Resolve("bind-address"))
	return http.ListenAndServe(config.Resolve("bind-address"), chitin.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := chitin.Context(w, r)
		h.ServeHTTPC(ctx, w, r)
	}), chitin.SetBaseContext(ctx)))
}

// RollbarMiddleware intercepts panics and sends them to rollbar before re-panicing
func RollbarMiddleware(h goji.Handler) goji.Handler {
	return goji.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		defer func() {
			if p := recover(); p != nil {
				if config.RollbarErrorLogger() != nil {
					config.RollbarErrorLogger().RequestPanic(r, p)
				} else {
					// This error handling is copied from golang's net/http package
					// https://github.com/golang/go/blob/0ec62565f911575beaf7d047dfe1eae2ae02bf67/src/net/http/server.go#L1468
					// It allows us to log the error while still returning a response to the client
					const size = 64 << 10
					buf := make([]byte, size)
					buf = buf[:runtime.Stack(buf, false)]
					log.Printf("http: panic serving %v: %v\n%s", r.RemoteAddr, p, buf)
				}

				w.WriteHeader(http.StatusInternalServerError)
			}
		}()

		h.ServeHTTPC(ctx, w, r)
	})
}

// SkipMiddleware takes a handler, and if the request is being routed to that handler
// it will skip all remaining middleware and immediately execute the handler.
//
// The order in which middleware is registered determines which middleware this will skip.
// For more information refer to https://godoc.org/goji.io#Mux.Use
func SkipMiddleware(desiredHandler goji.Handler) func(goji.Handler) goji.Handler {
	return func(h goji.Handler) goji.Handler {
		return goji.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
			if handler := middleware.Handler(ctx); handler == desiredHandler {
				handler.ServeHTTPC(ctx, w, r)
				return
			}

			h.ServeHTTPC(ctx, w, r)
		})
	}
}

func healthcheck(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
	_, err := w.Write([]byte("OK"))
	if err != nil {
		log.Printf("health check failed to respond: %v", err)
	}
}
