package interceptor

import (
	"context"
	"fmt"
	"net/http"
	"sync"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/observability/pkg/globalservice"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"github.com/tidbcloud/tiinf/transport/http/tigin"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/client"
	"go.uber.org/zap"
)

var (
	accountClients  sync.Map
	metaCacheGetter globalservice.MetaCacheGetter
)

// getAccountProviderSvcClient lazy initialize the account client for corresponding bizType(cloud dev/stag/prod)
func getAccountProviderSvcClient(bizType string) (account.AccountAPIServiceClient, error) {
	if cli, ok := accountClients.Load(bizType); ok {
		return cli.(account.AccountAPIServiceClient), nil
	}

	addr, err := getAccountProviderAddrByBizType(bizType)
	if err != nil || addr == "" {
		return nil, err
	}

	conn, err := tigrpc.NewClientConn(addr, tigrpc.WithDefault())
	if err != nil {
		return nil, err
	}

	cli := account.NewAccountAPIServiceClient(conn)
	accountClients.Store(bizType, cli)
	return cli, nil
}

func getAccountProviderAddrByBizType(bizType string) (string, error) {
	if metaCacheGetter == nil {
		metaCacheGetter = globalservice.GetGlobalManager()
	}
	config := metaCacheGetter.GetGlobalMetaCache().ServerConfig

	addr, ok := config.BizToAccountProviderSvcAddr[bizType]
	if !ok {
		return "", fmt.Errorf("unsupported bizType %s", bizType)
	}
	return addr, nil
}

func UserAuth(c *gin.Context) {
	bizType := c.Param("bizType")
	cli, err := getAccountProviderSvcClient(bizType)
	if err != nil {
		log.Error("getAccountProviderSvcClient failed", zap.Error(err), zap.String("Biz_Type", bizType))
		authError(c, "Unauthorized")
		return
	}
	tigin.CheckUserAndPermission(cli)(c)
}

func authError(c *gin.Context, msg string) {
	resp := map[string]string{"error": msg}
	c.JSON(http.StatusUnauthorized, resp)
	c.Abort()
}

func ginGetIdentityFunc(ctx context.Context, param *common.AccessParam) (string, error) {
	c := param.Req.(*gin.Context)
	user, err := tigin.GetCurrentUser(c)
	if err != nil {
		// if user is not set, check accesskey-based identity
		info, idErr := auth.GetIdentityInfoFromGinContext(c)
		if idErr == nil && info != nil {
			return fmt.Sprintf("%d", info.IdentityID), nil
		}
		return "", err
	}
	return fmt.Sprintf("%d", user.Id), nil
}
