// Package server is documented in router.go. server.go owns the HTTP listener
// lifecycle: it binds the configured TCP listener, serves the supplied
// http.Handler, and shuts down within the configured budget.
package server

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

	"galaxy/backend/internal/config"

	"go.uber.org/zap"
)

// Server owns the HTTP listener exposed by the backend.
type Server struct {
	cfg     config.HTTPConfig
	handler http.Handler
	logger  *zap.Logger

	stateMu  sync.RWMutex
	server   *http.Server
	listener net.Listener
}

// NewServer constructs an HTTP server bound to cfg. handler is the prebuilt
// http.Handler returned by NewRouter. A nil logger is replaced with zap.NewNop.
func NewServer(cfg config.HTTPConfig, handler http.Handler, logger *zap.Logger) *Server {
	if logger == nil {
		logger = zap.NewNop()
	}
	if handler == nil {
		handler = http.NotFoundHandler()
	}

	return &Server{
		cfg:     cfg,
		handler: handler,
		logger:  logger.Named("http"),
	}
}

// Run binds the listener and serves requests until Shutdown closes the server.
func (s *Server) Run(ctx context.Context) error {
	if ctx == nil {
		return errors.New("run backend HTTP server: nil context")
	}
	if err := ctx.Err(); err != nil {
		return err
	}

	listener, err := net.Listen("tcp", s.cfg.Addr)
	if err != nil {
		return fmt.Errorf("run backend HTTP server: listen on %q: %w", s.cfg.Addr, err)
	}

	server := &http.Server{
		Handler:      s.handler,
		ReadTimeout:  s.cfg.ReadTimeout,
		WriteTimeout: s.cfg.WriteTimeout,
		IdleTimeout:  s.cfg.ReadTimeout,
	}

	s.stateMu.Lock()
	s.server = server
	s.listener = listener
	s.stateMu.Unlock()

	s.logger.Info("backend HTTP server started", zap.String("addr", listener.Addr().String()))

	defer func() {
		s.stateMu.Lock()
		s.server = nil
		s.listener = nil
		s.stateMu.Unlock()
	}()

	err = server.Serve(listener)
	switch {
	case err == nil:
		return nil
	case errors.Is(err, http.ErrServerClosed):
		s.logger.Info("backend HTTP server stopped")
		return nil
	default:
		return fmt.Errorf("run backend HTTP server: serve on %q: %w", s.cfg.Addr, err)
	}
}

// Shutdown gracefully stops the HTTP server within ctx, applying the
// configured per-listener shutdown timeout when it is shorter.
func (s *Server) Shutdown(ctx context.Context) error {
	if ctx == nil {
		return errors.New("shutdown backend HTTP server: nil context")
	}

	s.stateMu.RLock()
	server := s.server
	s.stateMu.RUnlock()

	if server == nil {
		return nil
	}

	shutdownCtx, cancel := boundedContext(ctx, s.cfg.ShutdownTimeout)
	defer cancel()

	if err := server.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
		return fmt.Errorf("shutdown backend HTTP server: %w", err)
	}
	return nil
}

func boundedContext(parent context.Context, limit time.Duration) (context.Context, context.CancelFunc) {
	if limit <= 0 {
		return context.WithCancel(parent)
	}
	return context.WithTimeout(parent, limit)
}
