package server

import (
	"bufio"
	"errors"
	"fmt"
	"net"
	"net/http"
	"reflect"
	"strings"
	"time"
	"unsafe"

	"github.com/tidbcloud/account/commons/metrics"

	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)

// ResponseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP Status code
type ResponseLogger struct {
	W      http.ResponseWriter
	Status int
}

func (l *ResponseLogger) Header() http.Header {
	return l.W.Header()
}

func (l *ResponseLogger) Write(b []byte) (int, error) {
	size, err := l.W.Write(b)
	return size, err
}

func (l *ResponseLogger) WriteHeader(s int) {
	l.W.WriteHeader(s)
	l.Status = s
}

func (l *ResponseLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if h, ok := l.W.(http.Hijacker); ok {
		return h.Hijack()
	}
	return nil, nil, errors.New("the internal writer is not a http.Hijacker instance")
}

type Handler struct {
	Pattern     runtime.Pattern
	HandlerFunc runtime.HandlerFunc
}

type httpMetricsHandler struct {
	handlers map[string][]Handler
}

func newHttpMetricsHandler(mux *runtime.ServeMux) *httpMetricsHandler {
	pointerVal := reflect.ValueOf(mux)
	val := reflect.Indirect(pointerVal)
	member := val.FieldByName("handlers")
	ptrToHandlers := unsafe.Pointer(member.UnsafeAddr()) //nolint:gosec
	handlers := (*map[string][]Handler)(ptrToHandlers)
	return &httpMetricsHandler{*handlers}
}

func (hd *httpMetricsHandler) Handler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		tBegin := time.Now()
		rl := &ResponseLogger{W: w, Status: 200}
		h.ServeHTTP(rl, r)
		path := hd.getURLPattern(r)
		if path != "" {
			method := strings.ToLower(r.Method)
			statusCodeStr := fmt.Sprintf("%d", rl.Status)
			duration := float64(time.Since(tBegin)) / float64(time.Millisecond)
			metrics.HttpRequestDuration.WithLabelValues(path, method, statusCodeStr).Observe(duration)
		}
	})
}

func (hd *httpMetricsHandler) getURLPattern(r *http.Request) string {
	path := r.URL.Path
	if strings.HasPrefix(path, "/") {
		components := strings.Split(path[1:], "/")
		l := len(components)
		var verb string
		if idx := strings.LastIndex(components[l-1], ":"); idx > 0 {
			c := components[l-1]
			components[l-1], verb = c[:idx], c[idx+1:]
		}
		for _, h := range hd.handlers[r.Method] {
			_, err := h.Pattern.Match(components, verb)
			if err != nil {
				continue
			}
			return h.Pattern.String()
		}
	}
	return ""
}
