package server

import (
	"context"
	"net/http"

	"github.com/fnproject/fn/api/common"
	"github.com/fnproject/fn/fnext"
	"github.com/gin-gonic/gin"
)

type middlewareController struct {
	// NOTE: I tried to make this work as if it were a normal context, but it just doesn't work right. If someone
	// does something like context.WithValue, then the return is a new &context.valueCtx{} which can't be cast. So now stuffing it into a value instead.
	// context.Context

	// separating this out so we can use it and don't have to reimplement context.Context above
	ginContext *gin.Context
	server     *Server
}

func (s *Server) apiMiddlewareWrapper() gin.HandlerFunc {
	return func(c *gin.Context) {
		// fmt.Println("api middleware")
		s.runMiddleware(c, s.apiMiddlewares)
	}
}

func (s *Server) rootMiddlewareWrapper() gin.HandlerFunc {
	return func(c *gin.Context) {
		// fmt.Println("ROOT MIDDLE")
		s.runMiddleware(c, s.rootMiddlewares)
	}
}

// This is basically a single gin middleware that runs a bunch of fn middleware.
// The final handler will pass it back to gin for further processing.
func (s *Server) runMiddleware(c *gin.Context, ms []fnext.Middleware) {
	// fmt.Println("runMiddleware")
	if len(ms) == 0 {
		// fmt.Println("ZERO MIDDLEWARE")
		c.Next()
		return
	}
	defer func() {
		//This is so that if the server errors or panics on a middleware the server will still respond and not send eof to client.
		err := recover()
		if err != nil {
			common.Logger(c.Request.Context()).WithField("MiddleWarePanicRecovery:", err).Errorln("A panic occurred during middleware.")
			handleErrorResponse(c, ErrInternalServerError)
		}
	}()

	ctx := context.WithValue(c.Request.Context(), fnext.MiddlewareControllerKey, s.newMiddlewareController(c))
	last := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// fmt.Println("final handler called")
		ctx := r.Context()
		c.Request = r.WithContext(ctx)
		c.Next()
	})

	chainAndServe(ms, c.Writer, c.Request.WithContext(ctx), last)

	c.Abort() // we always abort here because the middleware decides to call next or not. If the `last` handler gets called, it will continue, otherwise we abort.
}

func (s *Server) newMiddlewareController(c *gin.Context) *middlewareController {
	return &middlewareController{
		ginContext: c,
		server:     s,
	}
}

// TODO: I will remove this and other debug commented lines once I'm sure it's all right.
func debugH(i int, m fnext.Middleware, h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// fmt.Println("handling", i, "m:", reflect.TypeOf(m), "h:", reflect.TypeOf(h))
		h.ServeHTTP(w, r) // call original
	})
}

// chainAndServe essentially makes a chain of middleware wrapped around each other, then calls ServerHTTP on the end result.
// then each middleware also calls ServeHTTP within it
func chainAndServe(ms []fnext.Middleware, w http.ResponseWriter, r *http.Request, last http.Handler) {
	h := last
	// These get chained in reverse order so they play out in the right order. Don't ask.
	for i := len(ms) - 1; i >= 0; i-- {
		m := ms[i]
		h = m.Handle(debugH(i, m, h))
	}
	h.ServeHTTP(w, r)
}

// AddMiddleware DEPRECATED - see AddAPIMiddleware
func (s *Server) AddMiddleware(m fnext.Middleware) {
	s.AddAPIMiddleware(m)
}

// AddMiddlewareFunc DEPRECATED - see AddAPIMiddlewareFunc
func (s *Server) AddMiddlewareFunc(m fnext.MiddlewareFunc) {
	s.AddAPIMiddlewareFunc(m)
}

// AddAPIMiddleware add middleware
func (s *Server) AddAPIMiddleware(m fnext.Middleware) {
	s.apiMiddlewares = append(s.apiMiddlewares, m)
}

// AddAPIMiddlewareFunc add middlewarefunc
func (s *Server) AddAPIMiddlewareFunc(m fnext.MiddlewareFunc) {
	s.AddAPIMiddleware(m)
}

// AddRootMiddleware add middleware add middleware for end user applications
func (s *Server) AddRootMiddleware(m fnext.Middleware) {
	s.rootMiddlewares = append(s.rootMiddlewares, m)
}

// AddRootMiddlewareFunc add middleware for end user applications
func (s *Server) AddRootMiddlewareFunc(m fnext.MiddlewareFunc) {
	s.AddRootMiddleware(m)
}
