package ops

import (
	"context"
	"errors"
	"net/http"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/tech/sendico/pkg/api/routers"
	"github.com/tech/sendico/pkg/api/routers/health"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

const (
	defaultAddress        = ":9420"
	readHeaderTimeout     = 5 * time.Second
	defaultShutdownWindow = 5 * time.Second
)

type httpServer struct {
	logger  mlogger.Logger
	server  *http.Server
	health  routers.Health
	timeout time.Duration
}

func newHTTPServer(logger mlogger.Logger, cfg HTTPServerConfig) (HTTPServer, error) {
	if logger == nil {
		return nil, merrors.InvalidArgument("ops: logger is nil")
	}

	address := strings.TrimSpace(cfg.Address)
	if address == "" {
		address = defaultAddress
	}

	r := chi.NewRouter()
	r.Handle("/metrics", promhttp.Handler())

	metricsLogger := logger.Named("ops")
	var healthRouter routers.Health
	hr, err := routers.NewHealthRouter(metricsLogger, r, "")
	if err != nil {
		metricsLogger.Warn("Failed to initialise health router", zap.Error(err))
	} else {
		hr.SetStatus(health.SSStarting)
		healthRouter = hr
	}

	httpSrv := &http.Server{
		Addr:              address,
		Handler:           r,
		ReadHeaderTimeout: readHeaderTimeout,
	}

	wrapper := &httpServer{
		logger:  metricsLogger,
		server:  httpSrv,
		health:  healthRouter,
		timeout: defaultShutdownWindow,
	}

	go func() {
		metricsLogger.Info("Prometheus endpoint listening", zap.String("address", address))
		serveErr := httpSrv.ListenAndServe()
		if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
			metricsLogger.Error("Prometheus endpoint stopped unexpectedly", zap.Error(serveErr))
			if healthRouter != nil {
				healthRouter.SetStatus(health.SSTerminating)
			}
		}
	}()

	return wrapper, nil
}

func (s *httpServer) SetStatus(status health.ServiceStatus) {
	if s == nil || s.health == nil {
		return
	}
	s.health.SetStatus(status)
}

func (s *httpServer) Close(ctx context.Context) {
	if s == nil {
		return
	}

	if s.health != nil {
		s.health.SetStatus(health.SSTerminating)
		s.health.Finish()
		s.health = nil
	}

	if s.server == nil {
		return
	}

	shutdownCtx := ctx
	if shutdownCtx == nil {
		shutdownCtx = context.Background()
	}
	if s.timeout > 0 {
		var cancel context.CancelFunc
		shutdownCtx, cancel = context.WithTimeout(shutdownCtx, s.timeout)
		defer cancel()
	}

	if err := s.server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
		s.logger.Warn("Failed to stop metrics server", zap.Error(err))
	} else {
		s.logger.Info("Metrics server stopped")
	}
}
