package auth

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

	"github.com/gin-gonic/gin"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/metrics-integration/global/internal/domain/cmd/bootstrap/wrapper"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/account/accountdb"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/grpc/grpcli"
	"github.com/tidbcloud/metrics-integration/global/pkg/log"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
)

type consoleAPIAuth struct{}

func (c *consoleAPIAuth) Auth(ctx *gin.Context) {
	logger := log.GetGinLogger(ctx)
	user, err := c.getUserFromToken(ctx)
	if err != nil {
		logger.Warn("get user from token occur error", zap.Error(err))
		wrapper.NewAPIError(ctx, http.StatusUnauthorized, UnauthenticatedError)
		return
	}

	getAuthUserRequest := &account.GetAuthUserRequest{
		UserEmail: user.Email,
	}
	orgID, ok := ctx.Get(OrgID)
	if ok {
		getAuthUserRequest.OrgId = orgID.(uint64)
		getAuthUserRequest.SkipCheckOrg = false
	} else {
		getAuthUserRequest.OrgId = 0
		getAuthUserRequest.SkipCheckOrg = true
	}

	authUser, bizErr := grpcli.GetAuthUser(context.Background(), getAuthUserRequest)
	if bizErr != nil {
		logger.Warn("Get auth user failed", zap.Any("getAuthReq", getAuthUserRequest), zap.Error(err))
		if bizErr.RawErrCode() == bizerr.ErrCodeRecordNotFound {
			wrapper.NewAPIError(ctx, http.StatusUnauthorized, UnauthenticatedError)
			return
		}
		wrapper.NewAPIError(ctx, http.StatusForbidden, UserPermissionDeniedError)
		return
	}

	// check the impersonated user first
	if authUser.IsImpersonated {
		method := strings.ToUpper(ctx.Request.Method)
		if method != "GET" && method != "OPTIONS" {
			wrapper.NewAPIError(ctx, http.StatusForbidden, ImpersonatedUserPermissionDeniedError)
			return
		}
	}

	ctx.Set(MetricsIntegrationDefineUser, accountdb.ConvertAuthUserToUser(authUser))
	return

}

func (c *consoleAPIAuth) getUserFromToken(ctx *gin.Context) (*account.User, error) {
	token := ctx.GetHeader(ConsoleAPITokenKey)
	if token == "" {
		return nil, errors.New("no header found with `Authorization`")
	}
	items := strings.Split(token, " ")
	if len(items) != 2 {
		return nil, errors.New("token not found with header `Authorization`")
	}
	if strings.ToLower(items[0]) != ConsoleAPITokenParam {
		return nil, errors.New(fmt.Sprintf("token type:%s mismatch with header `Authorization`", items[0]))
	}
	token = items[1]
	user, err := c.getUserByToken(ctx, token)
	if err != nil || user == nil {
		return nil, errors.Annotate(err, fmt.Sprintf("failed to get user:%s by token", user))
	}
	return user, nil
}

func (c *consoleAPIAuth) getUserByToken(ctx context.Context, token string) (*account.User, bizerr.BizError) {
	var resp *account.GetUserByTokenResponse
	var err bizerr.BizError
	resp, err = grpcli.GetUserByToken(ctx, &account.GetUserByTokenRequest{Token: token})
	if err != nil {
		return nil, err
	}
	return resp.User, err
}
