package auth

import (
	"context"
	"regexp"
	"strings"

	"github.com/tidbcloud/pkgv2/auth/auth0"
	"github.com/tidbcloud/serverless-service/internal/config"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	grpc_http "github.com/tidbcloud/serverless-service/internal/grpc/http"
	"google.golang.org/grpc"
)

var ImpersonatedPOSTURLWhiteList = []*regexp.Regexp{regexp.MustCompile(`.*/query$`)}

type Handler interface {
	Auth(req AuthReq) (context.Context, error)
}

type AuthReq struct {
	ctx              context.Context
	testTokenChecker *TestTokenChecker
	tokenValidator   *auth0.RawTokenValidator
}

func GetAuthHandler(ctx context.Context) Handler {
	url := grpc_http.HttpURI(ctx)

	// Open API
	if strings.HasPrefix(url, "/api/internal/") || strings.HasPrefix(url, "/api/v1beta/") ||
		strings.HasPrefix(url, "/v1beta1/") {
		return openApiSingleton
	}
	// web console API
	return consoleApiSingleton
}

func FilterHttpRequest(url, method string) bool {
	switch method {
	case "GET", "OPTIONS":
		return false
	case "POST":
		for _, allowPattern := range ImpersonatedPOSTURLWhiteList {
			if allowPattern.MatchString(url) {
				return false
			}
		}
		return true
	default:
		return true
	}
}

func UnaryServerInterceptor(
	testTokenChecker *TestTokenChecker,
	tokenValidator *auth0.RawTokenValidator,
) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
		if localCtx, ok := skipLoopback(ctx); ok {
			return handler(localCtx, req)
		}
		authHandler := GetAuthHandler(ctx)
		newCtx, err := authHandler.Auth(AuthReq{
			ctx:              ctx,
			testTokenChecker: testTokenChecker,
			tokenValidator:   tokenValidator,
		})
		if err != nil {
			return newCtx, err
		}
		return handler(newCtx, req) //nolint:contextcheck
	}
}

func skipLoopback(ctx context.Context) (context.Context, bool) {
	if !config.IsTestStage() {
		return ctx, false
	}

	clientIP := grpc_http.ClientIP(ctx)
	if clientIP != "127.0.0.1" {
		return ctx, false
	}

	fakeUser := &facade.User{
		ID:       0,
		TenantID: 0,
		Email:    "loopback@host",
		Status:   constant.UserStatusActive,
		Projects: []facade.Project{{
			ID:       0,
			TenantID: 0,
			Status:   constant.ProjectStatusActive,
		}},
		LastName: "Loopback",
	}

	newCtx := context.WithValue(ctx, auth0.AuthTokenClaimKey, fakeUser)
	newCtx = context.WithValue(newCtx, auth0.AuthUserIDKey, fakeUser.Email)
	newCtx = context.WithValue(newCtx, config.CtxGRPCGatewayTokenKey, config.CtxGRPCGatewayTokenValue)
	url := grpc_http.HttpURI(ctx)
	if strings.HasPrefix(url, "/api/internal/") || strings.HasPrefix(url, "/api/v1beta/") ||
		strings.HasPrefix(url, "/v1beta1/") {
		newCtx = context.WithValue(newCtx, auth0.AuthTypeKey, config.ApiKeyAuthType)
		newCtx = context.WithValue(newCtx, config.CtxIdentityKey, fakeUser.LastName)
	} else {
		newCtx = context.WithValue(newCtx, auth0.AuthTypeKey, config.MailAuthType)
	}
	return newCtx, true
}
