// Copyright 2025 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package frontend

import (
	"container/list"
	"net/http"
)

// MiddlewareFunc specifies the call signature for middleware functions.
// At some point during normal execution, the middleware function must call
// the "next" handler function to invoke the next layer of request handling.
type MiddlewareFunc func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)

// Middleware is a list of middleware functions to execute before invoking an http.Handler.
type Middleware struct {
	functions list.List
}

// NewMiddleware allocates and returns a new Middleware.
func NewMiddleware(functions ...MiddlewareFunc) *Middleware {
	m := &Middleware{}
	m.init(functions...)
	return m
}

func (m *Middleware) init(functions ...MiddlewareFunc) {
	for _, item := range functions {
		m.functions.PushBack(item)
	}
}

// nextMiddleware returns the function that middleware functions receive as
// their "next" argument. The returned function invokes the next middleware
// function in the list if one exists, or else the final HTTP handler.
func (m *Middleware) nextMiddleware(el *list.Element, handler http.Handler) http.HandlerFunc {
	if el != nil {
		return func(w http.ResponseWriter, r *http.Request) {
			el.Value.(MiddlewareFunc)(w, r, m.nextMiddleware(el.Next(), handler))
		}
	}
	return handler.ServeHTTP
}

// Handler returns an http.Handler that invokes the list of middleware
// functions before invoking the given HTTP handler. Pass the returned
// http.Handler to http.ServeMux.Handle to add middleware functions that
// execute after pattern-based multiplexing occurs and values for path
// wildcards are available via http.Request.PathValue.
func (m *Middleware) Handler(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		m.nextMiddleware(m.functions.Front(), handler)(w, r)
	})
}

// HandlerFunc returns an http.Handler that invokes the list of middleware
// functions before invoking the given HTTP handler function. Pass the returned
// http.Handler to http.ServeMux.Handle to add middleware functions that
// execute after pattern-based multiplexing occurs and values for path
// wildcards are available via http.Request.PathValue.
func (m *Middleware) HandlerFunc(handler func(http.ResponseWriter, *http.Request)) http.Handler {
	return m.Handler(http.HandlerFunc(handler))
}

// MiddlewareMux is an http.ServeMux with middleware functions that execute
// before pattern-based multiplexing occurs.
type MiddlewareMux struct {
	http.ServeMux
	middleware Middleware
}

// NewMiddlewareMux allocates and returns a new MiddlewareMux.
func NewMiddlewareMux(functions ...MiddlewareFunc) *MiddlewareMux {
	mux := &MiddlewareMux{}
	mux.middleware.init(functions...)
	return mux
}

// ServeHTTP dispatches the request to each middleware function, and then to
// the handler whose pattern most closely matches the request URL.
func (mux *MiddlewareMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Initialize a string pointer to record the pattern matched by ServeMux.
	//
	// This is useful for middlewares that are executed before ServeMux and
	// want to know the matched pattern. They can't rely on the value stored in
	// r.Pattern because the original request can be mutated by following
	// middlewares in which case r.Pattern remains empty.
	//
	// Since the handlers execute sequentially, there's no risk of concurrent
	// access to the value.
	patt := new(string)
	r = r.WithContext(ContextWithPattern(r.Context(), patt))

	mainHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Capture the matched pattern when ServeMux returns or when a matched
		// handler panics, so pre-mux middleware can read it from context.
		defer func() {
			*patt = r.Pattern
		}()
		mux.ServeMux.ServeHTTP(w, r)
	})

	mux.middleware.Handler(mainHandler).ServeHTTP(w, r)
}
