package gateway

import (
	"errors"
	"fmt"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/devops/api"
	jmpent "github.com/tidbcloud/devops/entity/jump"
	ctrl "github.com/tidbcloud/devops/pkg/controller"
	"github.com/tidbcloud/devops/pkg/infra/config"
	authutil "github.com/tidbcloud/devops/pkg/infra/utils/auth"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
)

type Auth struct {
	tokenValidator authutil.TokenValidator
}

func NewAuth(conf *config.JumpGatewayAuthConfig) (*Auth, error) {
	var err error
	a := &Auth{}

	opt := authutil.TokenValidatorOptions{
		JWKSURL:  conf.DevOpsJWKSURL,
		Audience: conf.Audience,
	}
	a.tokenValidator, err = authutil.NewTokenValidator(opt)
	if err != nil {
		return nil, fmt.Errorf("init token validator failed: %w", err)
	}

	return a, nil
}

func (a *Auth) Middleware(ctx *gin.Context) {
	claims, err := a.authn(ctx)
	if err != nil {
		ctrl.FailWithCode(ctx, err, api.ErrCodeUnauthenticated)
		return
	}

	ctx.Set(CtxKeyGatewayClaims, claims)
	ctx.Next()
}

func (a *Auth) authn(ctx *gin.Context) (*jmpent.GatewayTokenClaims, error) {
	token := ctrl.GetAccessTokenFromContext(ctx)
	if token == "" {
		return nil, errors.New("missing token")
	}

	rawClaims, err := a.tokenValidator.ValidateToken(ctx, token)
	if err != nil {
		return nil, err
	}

	gwClaims := &jmpent.GatewayTokenClaims{}
	err = pointer.MapToStruct(rawClaims, gwClaims)
	if err != nil {
		return nil, fmt.Errorf("map claims to struct failed: %w", err)
	}

	return gwClaims, nil
}
