package middleware

import (
	"context"
	"errors"
	"net/http"

	"github.com/zitadel/zitadel/internal/api/authz"
	http_util "github.com/zitadel/zitadel/internal/api/http"
	"github.com/zitadel/zitadel/internal/telemetry/tracing"
)

type AuthInterceptor struct {
	verifier   authz.APITokenVerifier
	authConfig authz.Config
}

func AuthorizationInterceptor(verifier authz.APITokenVerifier, authConfig authz.Config) *AuthInterceptor {
	return &AuthInterceptor{
		verifier:   verifier,
		authConfig: authConfig,
	}
}

func (a *AuthInterceptor) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx, err := authorize(r, a.verifier, a.authConfig)
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}
		r = r.WithContext(ctx)
		next.ServeHTTP(w, r)
	})
}

func (a *AuthInterceptor) HandlerFunc(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, err := authorize(r, a.verifier, a.authConfig)
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnauthorized)
			return
		}
		r = r.WithContext(ctx)
		next.ServeHTTP(w, r)
	}
}

type httpReq struct{}

func authorize(r *http.Request, verifier authz.APITokenVerifier, authConfig authz.Config) (_ context.Context, err error) {
	ctx := r.Context()
	authOpt, needsToken := verifier.CheckAuthMethod(r.Method + ":" + r.RequestURI)
	if !needsToken {
		return ctx, nil
	}
	authCtx, span := tracing.NewServerInterceptorSpan(ctx)
	defer func() { span.EndWithError(err) }()

	authToken := http_util.GetAuthorization(r)
	if authToken == "" {
		return nil, errors.New("auth header missing")
	}

	ctxSetter, err := authz.CheckUserAuthorization(authCtx, &httpReq{}, authToken, http_util.GetOrgID(r), "", verifier, authConfig, authOpt, r.RequestURI)
	if err != nil {
		return nil, err
	}
	span.End()
	return ctxSetter(ctx), nil
}