// Copyright 2015 Matthew Holt and The Caddy Authors
//
// 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 caddyhttp

import (
	"encoding/json"
	"fmt"
	"net/http"
	"slices"
	"strings"

	"github.com/caddyserver/caddy/v2"
)

// Route consists of a set of rules for matching HTTP requests,
// a list of handlers to execute, and optional flow control
// parameters which customize the handling of HTTP requests
// in a highly flexible and performant manner.
type Route struct {
	// Group is an optional name for a group to which this
	// route belongs. Grouping a route makes it mutually
	// exclusive with others in its group; if a route belongs
	// to a group, only the first matching route in that group
	// will be executed.
	Group string `json:"group,omitempty"`

	// The matcher sets which will be used to qualify this
	// route for a request (essentially the "if" statement
	// of this route). Each matcher set is OR'ed, but matchers
	// within a set are AND'ed together.
	MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"`

	// The list of handlers for this route. Upon matching a request, they are chained
	// together in a middleware fashion: requests flow from the first handler to the last
	// (top of the list to the bottom), with the possibility that any handler could stop
	// the chain and/or return an error. Responses flow back through the chain (bottom of
	// the list to the top) as they are written out to the client.
	//
	// Not all handlers call the next handler in the chain. For example, the reverse_proxy
	// handler always sends a request upstream or returns an error. Thus, configuring
	// handlers after reverse_proxy in the same route is illogical, since they would never
	// be executed. You will want to put handlers which originate the response at the very
	// end of your route(s). The documentation for a module should state whether it invokes
	// the next handler, but sometimes it is common sense.
	//
	// Some handlers manipulate the response. Remember that requests flow down the list, and
	// responses flow up the list.
	//
	// For example, if you wanted to use both `templates` and `encode` handlers, you would
	// need to put `templates` after `encode` in your route, because responses flow up.
	// Thus, `templates` will be able to parse and execute the plain-text response as a
	// template, and then return it up to the `encode` handler which will then compress it
	// into a binary format.
	//
	// If `templates` came before `encode`, then `encode` would write a compressed,
	// binary-encoded response to `templates` which would not be able to parse the response
	// properly.
	//
	// The correct order, then, is this:
	//
	//     [
	//         {"handler": "encode"},
	//         {"handler": "templates"},
	//         {"handler": "file_server"}
	//     ]
	//
	// The request flows ⬇️ DOWN (`encode` -> `templates` -> `file_server`).
	//
	// 1. First, `encode` will choose how to `encode` the response and wrap the response.
	// 2. Then, `templates` will wrap the response with a buffer.
	// 3. Finally, `file_server` will originate the content from a file.
	//
	// The response flows ⬆️ UP (`file_server` -> `templates` -> `encode`):
	//
	// 1. First, `file_server` will write the file to the response.
	// 2. That write will be buffered and then executed by `templates`.
	// 3. Lastly, the write from `templates` will flow into `encode` which will compress the stream.
	//
	// If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes.
	HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"`

	// If true, no more routes will be executed after this one.
	Terminal bool `json:"terminal,omitempty"`

	// decoded values
	MatcherSets MatcherSets         `json:"-"`
	Handlers    []MiddlewareHandler `json:"-"`

	middleware  []Middleware
	metrics     *Metrics
	metricsCtx  caddy.Context
	handlerName string
}

// Empty returns true if the route has all zero/default values.
func (r Route) Empty() bool {
	return len(r.MatcherSetsRaw) == 0 &&
		len(r.MatcherSets) == 0 &&
		len(r.HandlersRaw) == 0 &&
		len(r.Handlers) == 0 &&
		!r.Terminal &&
		r.Group == ""
}

func (r Route) String() string {
	var handlersRaw strings.Builder
	handlersRaw.WriteByte('[')
	for _, hr := range r.HandlersRaw {
		handlersRaw.WriteByte(' ')
		handlersRaw.WriteString(string(hr))
	}
	handlersRaw.WriteByte(']')

	return fmt.Sprintf(`{Group:"%s" MatcherSetsRaw:%s HandlersRaw:%s Terminal:%t}`,
		r.Group, r.MatcherSetsRaw, handlersRaw.String(), r.Terminal)
}

// Provision sets up both the matchers and handlers in the route.
func (r *Route) Provision(ctx caddy.Context, metrics *Metrics) error {
	err := r.ProvisionMatchers(ctx)
	if err != nil {
		return err
	}
	return r.ProvisionHandlers(ctx, metrics)
}

// ProvisionMatchers sets up all the matchers by loading the
// matcher modules. Only call this method directly if you need
// to set up matchers and handlers separately without having
// to provision a second time; otherwise use Provision instead.
func (r *Route) ProvisionMatchers(ctx caddy.Context) error {
	// matchers
	matchersIface, err := ctx.LoadModule(r, "MatcherSetsRaw")
	if err != nil {
		return fmt.Errorf("loading matcher modules: %v", err)
	}
	err = r.MatcherSets.FromInterface(matchersIface)
	if err != nil {
		return err
	}
	return nil
}

// ProvisionHandlers sets up all the handlers by loading the
// handler modules. Only call this method directly if you need
// to set up matchers and handlers separately without having
// to provision a second time; otherwise use Provision instead.
func (r *Route) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error {
	handlersIface, err := ctx.LoadModule(r, "HandlersRaw")
	if err != nil {
		return fmt.Errorf("loading handler modules: %v", err)
	}
	for _, handler := range handlersIface.([]any) {
		r.Handlers = append(r.Handlers, handler.(MiddlewareHandler))
	}

	// Store metrics info for route-level instrumentation (applied once
	// per route in wrapRoute, instead of per-handler which was redundant).
	r.metrics = metrics
	r.metricsCtx = ctx
	if len(r.Handlers) > 0 {
		r.handlerName = caddy.GetModuleName(r.Handlers[0])
	}

	// Make ProvisionHandlers idempotent by clearing the middleware field
	r.middleware = []Middleware{}

	// pre-compile the middleware handler chain
	for _, midhandler := range r.Handlers {
		r.middleware = append(r.middleware, wrapMiddleware(ctx, midhandler))
	}
	return nil
}

// Compile prepares a middleware chain from the route list.
// This should only be done once during the request, just
// before the middleware chain is executed.
func (r Route) Compile(next Handler) Handler {
	return wrapRoute(r)(next)
}

// RouteList is a list of server routes that can
// create a middleware chain.
type RouteList []Route

// Provision sets up both the matchers and handlers in the routes.
func (routes RouteList) Provision(ctx caddy.Context) error {
	err := routes.ProvisionMatchers(ctx)
	if err != nil {
		return err
	}
	return routes.ProvisionHandlers(ctx, nil)
}

// ProvisionMatchers sets up all the matchers by loading the
// matcher modules. Only call this method directly if you need
// to set up matchers and handlers separately without having
// to provision a second time; otherwise use Provision instead.
func (routes RouteList) ProvisionMatchers(ctx caddy.Context) error {
	for i := range routes {
		err := routes[i].ProvisionMatchers(ctx)
		if err != nil {
			return fmt.Errorf("route %d: %v", i, err)
		}
	}
	return nil
}

// ProvisionHandlers sets up all the handlers by loading the
// handler modules. Only call this method directly if you need
// to set up matchers and handlers separately without having
// to provision a second time; otherwise use Provision instead.
func (routes RouteList) ProvisionHandlers(ctx caddy.Context, metrics *Metrics) error {
	for i := range routes {
		err := routes[i].ProvisionHandlers(ctx, metrics)
		if err != nil {
			return fmt.Errorf("route %d: %v", i, err)
		}
	}
	return nil
}

// Compile prepares a middleware chain from the route list.
// This should only be done either once during provisioning
// for top-level routes, or on each request just before the
// middleware chain is executed for subroutes.
func (routes RouteList) Compile(next Handler) Handler {
	mid := make([]Middleware, 0, len(routes))
	for _, route := range routes {
		mid = append(mid, wrapRoute(route))
	}
	stack := next
	for _, middleware := range slices.Backward(mid) {
		stack = middleware(stack)
	}
	return stack
}

// wrapRoute wraps route with a middleware and handler so that it can
// be chained in and defer evaluation of its matchers to request-time.
// Like wrapMiddleware, it is vital that this wrapping takes place in
// its own stack frame so as to not overwrite the reference to the
// intended route by looping and changing the reference each time.
func wrapRoute(route Route) Middleware {
	return func(next Handler) Handler {
		return HandlerFunc(func(rw http.ResponseWriter, req *http.Request) error {
			// TODO: Update this comment, it seems we've moved the copy into the handler?
			// copy the next handler (it's an interface, so it's just
			// a very lightweight copy of a pointer); this is important
			// because this is a closure to the func below, which
			// re-assigns the value as it compiles the middleware stack;
			// if we don't make this copy, we'd affect the underlying
			// pointer for all future request (yikes); we could
			// alternatively solve this by moving the func below out of
			// this closure and into a standalone package-level func,
			// but I just thought this made more sense
			nextCopy := next

			// route must match at least one of the matcher sets
			matches, err := route.MatcherSets.AnyMatchWithError(req)
			if err != nil {
				// allow matchers the opportunity to short circuit
				// the request and trigger the error handling chain
				return err
			}
			if !matches {
				// call the next handler, and skip this one,
				// since the matcher didn't match
				return nextCopy.ServeHTTP(rw, req)
			}

			// if route is part of a group, ensure only the
			// first matching route in the group is applied
			if route.Group != "" {
				groups := req.Context().Value(routeGroupCtxKey).(map[string]struct{})

				if _, ok := groups[route.Group]; ok {
					// this group has already been
					// satisfied by a matching route
					return nextCopy.ServeHTTP(rw, req)
				}

				// this matching route satisfies the group
				groups[route.Group] = struct{}{}
			}

			// make terminal routes terminate
			if route.Terminal {
				if _, ok := req.Context().Value(ErrorCtxKey).(error); ok {
					nextCopy = errorEmptyHandler
				} else {
					nextCopy = emptyHandler
				}
			}

			// compile this route's handler stack
			for _, middleware := range slices.Backward(route.middleware) {
				nextCopy = middleware(nextCopy)
			}

			// Apply metrics instrumentation once for the entire route,
			// rather than wrapping each individual handler. This avoids
			// redundant metrics collection that caused significant CPU
			// overhead (see issue #4644).
			if route.metrics != nil {
				nextCopy = newMetricsInstrumentedRoute(
					route.metricsCtx, route.handlerName, nextCopy, route.metrics,
				)
			}

			return nextCopy.ServeHTTP(rw, req)
		})
	}
}

// wrapMiddleware wraps mh such that it can be correctly
// appended to a list of middleware in preparation for
// compiling into a handler chain.
func wrapMiddleware(ctx caddy.Context, mh MiddlewareHandler) Middleware {
	return func(next Handler) Handler {
		return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
			// EXPERIMENTAL: Trace each module that gets invoked
			if server, ok := r.Context().Value(ServerCtxKey).(*Server); ok && server != nil {
				server.logTrace(mh)
			}
			return mh.ServeHTTP(w, r, next)
		})
	}
}

// MatcherSet is a set of matchers which
// must all match in order for the request
// to be matched successfully.
type MatcherSet []any

// Match returns true if the request matches all
// matchers in mset or if there are no matchers.
func (mset MatcherSet) Match(r *http.Request) bool {
	for _, m := range mset {
		if me, ok := m.(RequestMatcherWithError); ok {
			match, _ := me.MatchWithError(r)
			if !match {
				return false
			}
			continue
		}
		if me, ok := m.(RequestMatcher); ok {
			if !me.Match(r) {
				return false
			}
			continue
		}
		return false
	}
	return true
}

// MatchWithError returns true if r matches m.
func (mset MatcherSet) MatchWithError(r *http.Request) (bool, error) {
	for _, m := range mset {
		if me, ok := m.(RequestMatcherWithError); ok {
			match, err := me.MatchWithError(r)
			if err != nil || !match {
				return match, err
			}
			continue
		}
		if me, ok := m.(RequestMatcher); ok {
			if !me.Match(r) {
				// for backwards compatibility
				err, ok := GetVar(r.Context(), MatcherErrorVarKey).(error)
				if ok {
					// clear out the error from context since we've consumed it
					SetVar(r.Context(), MatcherErrorVarKey, nil)
					return false, err
				}
				return false, nil
			}
			continue
		}
		return false, fmt.Errorf("matcher is not a RequestMatcher or RequestMatcherWithError: %#v", m)
	}
	return true, nil
}

// RawMatcherSets is a group of matcher sets
// in their raw, JSON form.
type RawMatcherSets []caddy.ModuleMap

// MatcherSets is a group of matcher sets capable
// of checking whether a request matches any of
// the sets.
type MatcherSets []MatcherSet

// AnyMatch returns true if req matches any of the
// matcher sets in ms or if there are no matchers,
// in which case the request always matches.
//
// Deprecated: Use AnyMatchWithError instead.
func (ms MatcherSets) AnyMatch(req *http.Request) bool {
	for _, m := range ms {
		match, err := m.MatchWithError(req)
		if err != nil {
			SetVar(req.Context(), MatcherErrorVarKey, err)
			return false
		}
		if match {
			return match
		}
	}
	return len(ms) == 0
}

// AnyMatchWithError returns true if req matches any of the
// matcher sets in ms or if there are no matchers, in which
// case the request always matches. If any matcher returns
// an error, we cut short and return the error.
func (ms MatcherSets) AnyMatchWithError(req *http.Request) (bool, error) {
	for _, m := range ms {
		match, err := m.MatchWithError(req)
		if err != nil || match {
			return match, err
		}
	}
	return len(ms) == 0, nil
}

// FromInterface fills ms from an 'any' value obtained from LoadModule.
func (ms *MatcherSets) FromInterface(matcherSets any) error {
	for _, matcherSetIfaces := range matcherSets.([]map[string]any) {
		var matcherSet MatcherSet
		for _, matcher := range matcherSetIfaces {
			if m, ok := matcher.(RequestMatcherWithError); ok {
				matcherSet = append(matcherSet, m)
				continue
			}
			if m, ok := matcher.(RequestMatcher); ok {
				matcherSet = append(matcherSet, m)
				continue
			}
			return fmt.Errorf("decoded module is not a RequestMatcher or RequestMatcherWithError: %#v", matcher)
		}
		*ms = append(*ms, matcherSet)
	}
	return nil
}

// TODO: Is this used?
func (ms MatcherSets) String() string {
	var result strings.Builder
	result.WriteByte('[')
	for _, matcherSet := range ms {
		for _, matcher := range matcherSet {
			fmt.Fprintf(&result, " %#v", matcher)
		}
	}
	result.WriteByte(']')
	return result.String()
}

var routeGroupCtxKey = caddy.CtxKey("route_group")
