package server

import (
	"context"
	"fmt"
	"log/slog"
	"net"
	"net/http"
	"net/url"
	"time"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/httpz"
)

const (
	oauthRedirectCallbackPath = "/oauth/redirect"
)

// middleware is func type that allows for chaining middleware
type middleware func(http.HandlerFunc) http.HandlerFunc

// compileMiddleware takes the base http.HandlerFunc h
// and wraps it around the given list of middleware m
// The middlewares run in the order given.
func compileMiddleware(h http.HandlerFunc, m []middleware) http.HandlerFunc {
	if len(m) == 0 {
		return h
	}

	wrapped := h

	// loop in reverse to preserve middleware order
	for i := len(m) - 1; i >= 0; i-- {
		wrapped = m[i](wrapped) //nolint: gosec
	}

	return wrapped
}

type httpServer struct {
	log           *slog.Logger
	authHandler   authHandlerInterface
	tunnelHandler tunnelHandlerInterface

	idleTimeout         time.Duration
	listenerGracePeriod time.Duration
	readHeaderTimeout   time.Duration
	shutdownTimeout     time.Duration

	apiExternalURL *url.URL
}

func (s *httpServer) Run(ctx context.Context, listener net.Listener) error {
	srv := &http.Server{
		Handler:           s.constructHandler(),
		ReadHeaderTimeout: s.readHeaderTimeout,
		IdleTimeout:       s.idleTimeout,
	}
	return httpz.RunServer(ctx, srv, listener, s.listenerGracePeriod, s.shutdownTimeout)
}

func (s *httpServer) constructHandler() http.Handler {
	mux := http.NewServeMux()

	// API traffic
	baseAPIPath := fmt.Sprintf("%s%s", s.apiExternalURL.Hostname(), s.apiExternalURL.Path)
	// Catch-all for baseAPIPath - returns 404
	mux.HandleFunc(baseAPIPath, func(w http.ResponseWriter, r *http.Request) {
		http.NotFound(w, r)
	})
	// Specific API endpoints - these will match first due to longest-match.
	// Do not use the method in the pattern.
	// Otherwise, a non-supported method on that specific path falls through and will be handled as
	// user's workspace traffic because there is a catch-all "/" where we do not match on hostname.
	mux.Handle(fmt.Sprintf("%s%s", baseAPIPath, oauthRedirectCallbackPath), compileMiddleware(s.authHandler.oauthRedirectCallback, []middleware{}))

	// User's workspace traffic
	workspacesMiddlewares := []middleware{
		s.authHandler.middleware,
	}
	mux.Handle("/", compileMiddleware(s.tunnelHandler.tunnel, workspacesMiddlewares))

	return mux
}
