package useadapter

import (
	"context"
	"net/http"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"
	"go.uber.org/zap"

	"github.com/tidbcloud/tiinf/sdk/rbac"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
)

var DefaultAccessDecideCompleteExecFunc = defaultAccessDecideCompleteExecFunc

type AccessDecideCompleteExecFunc func(w http.ResponseWriter, r *http.Request, result *common.AccessResult, err error) (continueRun bool)

func defaultAccessDecideCompleteExecFunc(w http.ResponseWriter, r *http.Request, result *common.AccessResult, err error) (continueRun bool) {
	if err != nil || result.Code != common.RSP_CODE_RELEASE {
		tilog.PlainInfo("permision check failed. ", zap.Any("result", result), zap.Error(err))
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("permission denied"))
		return false
	}

	return true
}

func GenAccessParam4Http(r *http.Request) *common.AccessParam {
	param := common.AccessParam{
		Metadata: map[string]interface{}{
			common.METADATA_PATH:   r.URL.Path,
			common.METADATA_METHOD: r.Method,
		},
		Req: r,
	}
	return &param
}

// -------- for raw http HandlerFunc --------

func CustomizedHttpHandlerFunc(
	f http.HandlerFunc,
	identityF common.GetIdentityFunc,
	resourceMap map[string]string,
	pathWhitelist ...string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		checker := rbac.GetPermissionCheckerWithCommonParam(&rbac.PermissionCheckerCommonParam{
			IdentityF:               identityF,
			PathPattern2ResourceMap: resourceMap,
			PathWhitelist:           pathWhitelist,
		})
		result, err := checker.Handle(r.Context(), GenAccessParam4Http(r))
		if !DefaultAccessDecideCompleteExecFunc(w, r, result, err) {
			return
		}
		f(w, r)
	}
}

// ------- for raw http serve mux --------------

type CustomizedHttpServeMux struct {
	*http.ServeMux
	mws        []common.AccessDecisionHandler
	ctxDataKey interface{}
}

func NewCustomizedHttpServeMux(mux *http.ServeMux, key interface{}) *CustomizedHttpServeMux {
	return &CustomizedHttpServeMux{
		ServeMux:   mux,
		mws:        []common.AccessDecisionHandler{},
		ctxDataKey: key,
	}
}

func (m *CustomizedHttpServeMux) Use(mws ...common.AccessDecisionHandler) {
	if mws != nil && len(mws) > 0 {
		for _, mw := range mws {
			m.mws = append(m.mws, mw)
		}
	}
}

func (m *CustomizedHttpServeMux) Handle(pattern string, handler http.Handler) {
	h := m.injectMiddlewares(handler, m.mws...)
	m.ServeMux.Handle(pattern, h)
}

func (m *CustomizedHttpServeMux) HandleFunc(pattern string, handler http.HandlerFunc) {
	h := m.injectMiddlewares(handler, m.mws...)
	m.ServeMux.Handle(pattern, h)
}

func (m *CustomizedHttpServeMux) injectMiddlewares(h http.Handler, mws ...common.AccessDecisionHandler) http.Handler {
	if mws == nil || len(mws) == 0 {
		return h
	}

	chainer := func(mw common.AccessDecisionHandler, h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			result, err := mw.Handle(r.Context(), GenAccessParam4Http(r))
			if !DefaultAccessDecideCompleteExecFunc(w, r, result, err) {
				return
			}
			newr := r
			if result.Data != nil {
				ctx := context.WithValue(r.Context(), m.ctxDataKey, result.Data)
				newr = r.WithContext(ctx)
			}
			h.ServeHTTP(w, newr)
		})
	}

	chainedHandler := h
	n := len(mws)
	for i := n - 1; i >= 0; i-- {
		chainedHandler = chainer(mws[i], chainedHandler)
	}

	return chainedHandler
}

func ServeMuxPermissionCheckInterceptor(
	ctxIdentityKey interface{},
	resourceMap map[string]string,
	pathWhitelist ...string) common.AccessDecisionHandler {
	return rbac.GetPermissionCheckerWithCommonParam(&rbac.PermissionCheckerCommonParam{
		IdentityF:               helperimpl.NewContextStoreGetIdentityFunc(ctxIdentityKey),
		PathPattern2ResourceMap: resourceMap,
		PathWhitelist:           pathWhitelist,
	})
}

// ------- common serve mux, adapter for raw http mux or runtime mux --------------
// ------- prepend permission check logic before request dispatch ------

type CustomizedServeMux struct {
	http.Handler
	mws        []common.AccessDecisionHandler
	ctxDataKey interface{}
}

func NewCustomizedServeMux(handler http.Handler, key interface{}) *CustomizedServeMux {
	return &CustomizedServeMux{
		Handler:    handler,
		mws:        []common.AccessDecisionHandler{},
		ctxDataKey: key,
	}
}

func (m *CustomizedServeMux) Use(mws ...common.AccessDecisionHandler) {
	if mws != nil && len(mws) > 0 {
		for _, mw := range mws {
			m.mws = append(m.mws, mw)
		}
	}
}

func (m *CustomizedServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if m.mws == nil || len(m.mws) == 0 {
		m.Handler.ServeHTTP(w, r)
		return
	}
	newr := r
	for _, mw := range m.mws {
		result, err := mw.Handle(newr.Context(), GenAccessParam4Http(r))
		if !DefaultAccessDecideCompleteExecFunc(w, r, result, err) {
			return
		}
		if result.Data != nil {
			ctx := context.WithValue(r.Context(), m.ctxDataKey, result.Data)
			newr = r.WithContext(ctx)
		}
	}
	m.Handler.ServeHTTP(w, newr)
}
