package auth

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

	"braces.dev/errtrace"
	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	authsdk "github.com/tidbcloud/tiinf/sdk/auth"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/pkg/apimiddleware/grpcerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/pkg/config"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/pkg/consts"
)

var ignoreAuthPaths = []string{
	"/healthz",
	"/version",
	"/ping",
}

type authServiceImpl struct {
	account account.Client
}

func newAuthAuthService(acc account.Client) Service {
	return &authServiceImpl{account: acc}
}

func (a *authServiceImpl) CheckUserAndPermission() gin.HandlerFunc {
	if config.Global().Auth.LocalMode {
		return func(ctx *gin.Context) {}
	}

	return authsdk.GinGetIdentityInfoInterceptor(&authsdk.IdentityAuthOption{
		EnableLog: false,
	})
}

func (a *authServiceImpl) RemoveApiKeyHeader() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Request.Header.Del("X-Auth-Method")
		c.Request.Header.Del("X-Auth-Content")
		c.Next()
	}
}

func (a *authServiceImpl) TransformAuthnCtx(ctx *gin.Context) {
	// add white list
	if !isIgnoreAuthPath(ctx) {
		user, err := a.getCurrentUser(ctx)
		if err != nil {
			tilog.Error(ctx.Request.Context(), "failed to get user", zap.Error(err))
			grpcerror.NewOpenAPIError(ctx, http.StatusUnauthorized, consts.ErrUnauthenticated)
			return
		}

		// set user to context for rbac
		ctx.Set(consts.XAuthUser, user)
		tilog.Info(ctx.Request.Context(), "auth successful", zap.String("path", ctx.Request.URL.Path))
	}
}

func isIgnoreAuthPath(ctx *gin.Context) bool {
	return slices.Contains(ignoreAuthPaths, ctx.Request.URL.Path)
}

func (a *authServiceImpl) getCurrentUser(ctx *gin.Context) (*entity.User, error) {
	// get identity info
	identityInfo, err := authsdk.GetIdentityInfoFromGinContext(ctx)
	if err != nil {
		tilog.Error(ctx.Request.Context(), "failed to get identity info,", zap.Error(err))
		return nil, errtrace.Wrap(err)
	}

	var user *entity.User
	switch identityInfo.IdentityType {
	case authsdk.AuthnIdentityTypeUserToken:
		user, err = a.getCurrentUserByAuth0(ctx, identityInfo.UserTokenRsp)
	case authsdk.AuthnIdentityTypeApiKeyAK:
		user, err = a.getCurrentUserByAPIKey(ctx, identityInfo.ApiKeyRsp)
	case authsdk.AuthnIdentityTypeAccessToken:
		user, err = a.getCurrentUserByAccessToken(ctx, identityInfo.AccessTokenRsp)
	default:
		tilog.Error(
			ctx.Request.Context(),
			"unknown identity type",
			zap.String("identityType", string(identityInfo.IdentityType)),
		)
		err = fmt.Errorf("unknown identity type")
	}

	if err != nil {
		tilog.Error(ctx.Request.Context(), "failed to get current user", zap.Error(err))
		return nil, errtrace.Wrap(err)
	}

	return user, nil
}
