// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The AILERON Gateway Authors

package http

import (
	"fmt"
	"net/http"
	"path"
	"slices"
	"strings"

	v1 "github.com/aileron-gateway/aileron-gateway/apis/core/v1"
	"github.com/aileron-gateway/aileron-gateway/core"
	"github.com/aileron-gateway/aileron-gateway/kernel/api"
	"github.com/aileron-gateway/aileron-gateway/kernel/er"
)

// Handler returns http handler generated by the given spec.
// This function returns http methods, path pattern, http handler and error.
// The methods are compacted to contains only unique values.
func Handler(a api.API[*api.Request, *api.Response], spec *v1.HTTPHandlerSpec) (methods []string, patterns []string, h http.Handler, err error) {
	ms, err := api.ReferTypedObjects[core.Middleware](a, spec.Middleware...)
	if err != nil {
		return nil, nil, nil, err // Return err as-is.
	}

	handler, err := api.ReferObject(a, spec.Handler)
	if err != nil {
		return nil, nil, nil, err // Return err as-is.
	}

	if ps, ok := handler.(interface{ Patterns() []string }); ok {
		for _, p := range ps.Patterns() {
			pattern := path.Join(spec.Pattern, p)            // path.Join removes trailing slash.
			if strings.HasSuffix(p, "/") && pattern != "/" { // Keep trailing slash.
				pattern = pattern + "/"
			}
			patterns = append(patterns, pattern)
		}
		slices.Sort(patterns)                            // slices.Compact require sorts.
		patterns = slices.Clip(slices.Compact(patterns)) // Remove duplicates.
	}

	if m, ok := handler.(interface{ Methods() []string }); ok {
		methods = m.Methods()
		slices.Sort(methods)                           // slices.Compact require sorts.
		methods = slices.Clip(slices.Compact(methods)) // Remove duplicates.
	}

	h, ok := handler.(http.Handler)
	if !ok {
		return nil, nil, nil, &er.Error{
			Package:     ErrPkg,
			Type:        ErrTypeChain,
			Description: ErrDscAssert,
			Detail:      fmt.Sprintf("assert from %T to http.Handler", handler),
		}
	}

	return methods, patterns, MiddlewareChain(ms, h), nil
}

// MiddlewareChain returns a handler with given middleware applied.
// For example, when the 3 middleware and a terminator were give,
// returned Handler will be like below.
// The terminator must not be nil and nil middleware is ignored.
// This function panics when nil terminator is given.
//
//	middleware[0]( middleware[1]( middleware[2]( terminator ) ) )
func MiddlewareChain(middleware []core.Middleware, terminator http.Handler) http.Handler {
	h := terminator
	n := len(middleware)
	for i := range middleware {
		if middleware[n-1-i] == nil {
			continue
		}
		h = middleware[n-1-i].Middleware(h)
	}
	return h
}

// TripperwareChain returns a RoundTripper with given tripperware applied.
// For example, when the 3 tripperware and a terminator were give,
// returned RoundTripper will be like below.
// The terminator must not be nil and nil tripperware is ignored.
// This function panics when nil terminator is given.
//
//	tripperware[0]( tripperware[1]( tripperware[2]( terminator ) ) )
func TripperwareChain(tripperware []core.Tripperware, terminator http.RoundTripper) http.RoundTripper {
	t := terminator
	n := len(tripperware)
	for i := range tripperware {
		if tripperware[n-1-i] == nil {
			continue
		}
		t = tripperware[n-1-i].Tripperware(t)
	}
	return t
}
