package monitoring

import (
	"fmt"
	"net"
	"net/http/pprof"
	"strconv"

	"github.com/VictoriaMetrics/metrics"
	"github.com/giongto35/cloud-game/v3/pkg/config"
	"github.com/giongto35/cloud-game/v3/pkg/logger"
	"github.com/giongto35/cloud-game/v3/pkg/network/httpx"
)

const debugEndpoint = "/debug/pprof"
const metricsEndpoint = "/metrics"

type Monitoring struct {
	conf   config.Monitoring
	server *httpx.Server
	log    *logger.Logger
}

// New creates new monitoring service.
// The tag param specifies owner label for logs.
func New(conf config.Monitoring, baseAddr string, log *logger.Logger) *Monitoring {
	serv, err := httpx.NewServer(
		net.JoinHostPort(baseAddr, strconv.Itoa(conf.Port)),
		func(s *httpx.Server) httpx.Handler {
			h := s.Mux()
			if conf.ProfilingEnabled {
				h.Prefix(conf.URLPrefix + debugEndpoint)
				h.HandleFunc("/", pprof.Index).
					HandleFunc("/cmdline", pprof.Cmdline).
					HandleFunc("/profile", pprof.Profile).
					HandleFunc("/symbol", pprof.Symbol).
					HandleFunc("/trace", pprof.Trace).
					Handle("/allocs", pprof.Handler("allocs")).
					Handle("/block", pprof.Handler("block")).
					Handle("/goroutine", pprof.Handler("goroutine")).
					Handle("/heap", pprof.Handler("heap")).
					Handle("/mutex", pprof.Handler("mutex")).
					Handle("/threadcreate", pprof.Handler("threadcreate"))
			}
			if conf.MetricEnabled {
				h.Prefix(conf.URLPrefix)
				h.HandleFunc(metricsEndpoint, func(w httpx.ResponseWriter, _ *httpx.Request) {
					metrics.WritePrometheus(w, true)
				})
			}
			h.Prefix("")
			return h
		},
		httpx.WithPortRoll(true),
		httpx.HttpsRedirect(false),
		httpx.WithLogger(log),
	)
	if err != nil {
		log.Error().Err(err).Msg("couldn't start monitoring server")
	}
	return &Monitoring{conf: conf, server: serv, log: log}
}

func (m *Monitoring) Run() {
	m.printInfo()
	m.server.Run()
}

func (m *Monitoring) Stop() error {
	m.log.Info().Msg("Shutting down monitoring server")
	return m.server.Stop()
}

func (m *Monitoring) String() string {
	return fmt.Sprintf("monitoring::%s:%d", m.conf.URLPrefix, m.conf.Port)
}

func (m *Monitoring) GetMetricsPublicAddress() string {
	return m.server.GetProtocol() + "://" + m.server.Addr + m.conf.URLPrefix + metricsEndpoint
}

func (m *Monitoring) GetProfilingAddress() string {
	return m.server.GetProtocol() + "://" + m.server.Addr + m.conf.URLPrefix + debugEndpoint
}

func (m *Monitoring) printInfo() {
	message := m.log.Info()
	if m.conf.ProfilingEnabled {
		message = message.Str("profiler", m.GetProfilingAddress())
	}
	if m.conf.MetricEnabled {
		message = message.Str("prometheus", m.GetMetricsPublicAddress())
	}
	message.Msg("Monitoring")
}
