// Package httpserver is the shared HTTP scaffolding used by every
// service. It wires /health and /metrics and applies a graceful
// shutdown to whatever handlers the caller passes in.
package httpserver

import (
	"context"
	"errors"
	"log/slog"
	"net/http"
	"time"

	"git3.techno-world.net/lrosales/broad-announce/internal/observability"
)

// Server bundles the http.Server with its config and the prom registry
// the caller wants to expose on /metrics.
type Server struct {
	cfg      Config
	srv      *http.Server
	logger   *slog.Logger
	registry http.Handler
}

// Config is the bits the caller can change.
type Config struct {
	Addr            string
	ServiceName     string
	ShutdownGrace   time.Duration
	ReadTimeout     time.Duration
	WriteTimeout    time.Duration
	IdleTimeout     time.Duration
	MaxHeaderBytes  int
}

// New constructs a Server with the given ServiceName and a custom mux
// (the caller is expected to add /v1/* handlers; /health and /metrics
// are added automatically).
func New(cfg Config, logger *slog.Logger, reg http.Handler) *Server {
	if cfg.ReadTimeout == 0 {
		cfg.ReadTimeout = 10 * time.Second
	}
	if cfg.WriteTimeout == 0 {
		cfg.WriteTimeout = 30 * time.Second
	}
	if cfg.IdleTimeout == 0 {
		cfg.IdleTimeout = 60 * time.Second
	}
	if cfg.MaxHeaderBytes == 0 {
		cfg.MaxHeaderBytes = 1 << 20 // 1 MB
	}
	mux := http.NewServeMux()
	mux.Handle("/health", observability.HealthHandler(cfg.ServiceName))
	mux.Handle("/metrics", reg)
	return &Server{
		cfg:      cfg,
		logger:   logger,
		registry: reg,
		srv: &http.Server{
			Addr:           cfg.Addr,
			Handler:        mux,
			ReadTimeout:    cfg.ReadTimeout,
			WriteTimeout:   cfg.WriteTimeout,
			IdleTimeout:    cfg.IdleTimeout,
			MaxHeaderBytes: cfg.MaxHeaderBytes,
		},
	}
}

// Mux returns the underlying mux so the caller can add /v1/* routes.
func (s *Server) Mux() *http.ServeMux {
	return s.srv.Handler.(*http.ServeMux)
}

// Start runs ListenAndServe. Returns when the server stops.
func (s *Server) Start() error {
	s.logger.Info("http listening", "addr", s.cfg.Addr)
	if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		return err
	}
	return nil
}

// Shutdown gracefully stops the server.
func (s *Server) Shutdown(ctx context.Context) error {
	ctx, cancel := context.WithTimeout(ctx, s.cfg.ShutdownGrace)
	defer cancel()
	return s.srv.Shutdown(ctx)
}
