package httpquery

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

	"github.com/prometheus/prometheus/storage"
	"sigs.k8s.io/controller-runtime/pkg/log"
)

const (
	defaultReadHeaderTimeout = 5 * time.Second
	defaultShutdownTimeout   = 5 * time.Second
	maxRequestBodyBytes      = 1 << 20
)

// Config configures the read-only Prometheus HTTP query service. Queryable is
// intentionally the only storage capability accepted by this package.
type Config struct {
	Address           string
	Queryable         storage.Queryable
	LookbackDelta     time.Duration
	MaxSamples        int
	QueryTimeout      time.Duration
	MaxPoints         int
	ReadHeaderTimeout time.Duration
	ShutdownTimeout   time.Duration
	Now               func() time.Time
}

// Server serves only the explicitly registered Prometheus read APIs.
type Server struct {
	address         string
	handler         http.Handler
	readTimeout     time.Duration
	shutdownTimeout time.Duration
}

// New returns an HTTP query server backed by cfg.Queryable.
func New(cfg *Config) (*Server, error) {
	if cfg == nil {
		return nil, fmt.Errorf("HTTP query server: config is required")
	}
	if cfg.Address == "" || cfg.Address == "0" {
		return nil, fmt.Errorf("HTTP query server: address is required")
	}
	if cfg.Queryable == nil {
		return nil, fmt.Errorf("HTTP query server: queryable is required")
	}
	readTimeout := cfg.ReadHeaderTimeout
	if readTimeout <= 0 {
		readTimeout = defaultReadHeaderTimeout
	}
	shutdownTimeout := cfg.ShutdownTimeout
	if shutdownTimeout <= 0 {
		shutdownTimeout = defaultShutdownTimeout
	}

	apiHandler := newAPI(cfg).handler()
	return &Server{
		address: cfg.Address,
		handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Body != nil {
				r.Body = http.MaxBytesReader(w, r.Body, maxRequestBodyBytes)
			}
			apiHandler.ServeHTTP(w, r)
		}),
		readTimeout:     readTimeout,
		shutdownTimeout: shutdownTimeout,
	}, nil
}

// Handler returns the server's read-only HTTP handler.
func (s *Server) Handler() http.Handler {
	return s.handler
}

// Start runs the query server until ctx is canceled.
func (s *Server) Start(ctx context.Context) error {
	listener, err := net.Listen("tcp", s.address)
	if err != nil {
		return fmt.Errorf("HTTP query server: listen on %s: %w", s.address, err)
	}
	server := &http.Server{
		Handler:           s.handler,
		ReadHeaderTimeout: s.readTimeout,
	}
	serveErr := make(chan error, 1)
	go func() {
		serveErr <- server.Serve(listener)
	}()
	log.FromContext(ctx).Info("starting HTTP query server", "address", listener.Addr().String())

	select {
	case err := <-serveErr:
		if errors.Is(err, http.ErrServerClosed) {
			return nil
		}
		return fmt.Errorf("HTTP query server: serve: %w", err)
	case <-ctx.Done():
		shutdownCtx, cancel := context.WithTimeout(context.Background(), s.shutdownTimeout)
		defer cancel()
		if err := server.Shutdown(shutdownCtx); err != nil {
			return fmt.Errorf("HTTP query server: shut down: %w", err)
		}
		err := <-serveErr
		if err != nil && !errors.Is(err, http.ErrServerClosed) {
			return fmt.Errorf("HTTP query server: serve: %w", err)
		}
		return nil
	}
}
