package auth

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/account/idl/pbgen/proto/acl"
	"github.com/tidbcloud/dataflow-service/app/container"
	"github.com/tidbcloud/dataflow-service/app/controllers/open"
	"github.com/tidbcloud/dataflow-service/internal/cmd/bootstrap/server/wrapper"
	"github.com/tidbcloud/dataflow-service/pkg/account/accountdb"
	"github.com/tidbcloud/dataflow-service/pkg/account/accounttypes"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/pkgv2/auth/kong"
	"go.uber.org/zap"
)

type openAPIAuth struct{}

func (c *openAPIAuth) Auth(ctx *gin.Context) {
	logger := log.GetGinLogger(ctx)
	publicKey := c.queryPublicKey(ctx)
	if publicKey == nil {
		logger.Info("no header found with `Authorization`")
		wrapper.NewAPIError(ctx, http.StatusUnauthorized, UnauthenticatedError)
		return
	}
	authUser, err := c.getAuthUser(ctx, publicKey)
	if err != nil {
		wrapper.NewAPIError(ctx, http.StatusUnauthorized, APIKeyPermissionDeniedError)
		return
	}
	ctx.Set(DataflowDefineUser, authUser)
	ctx.Request.Header.Set(open.OpenAPIPublicKey, *publicKey)
}

func (c *openAPIAuth) getAuthUser(ctx *gin.Context, publicKey *string) (*accountdb.User, error) {
	logger := log.GetGinLogger(ctx)
	resp, err := container.Global().AccountAclGrpcClient.GetOpenAPIAccess(ctx,
		&acl.GetOpenAPIAccessReq{
			PublicKey: *publicKey,
		})
	if err != nil {
		logger.Warn("failed to get user by token", zap.Error(err))
		return nil, fmt.Errorf("failed to get user by token: %w", err)
	}

	if resp.Org == nil || len(resp.Projects) == 0 {
		logger.Warn("failed to get user by token", zap.Error(err))
		return nil, fmt.Errorf("failed to get user by token: %w", err)
	}
	systemUserResp, err := container.Global().AccountSvcGrpcClient.GetOrCreateSystemUser(ctx,
		&account.GetOrCreateSystemUserRequest{
			OrgId: resp.Org.Id,
		})
	if err != nil {
		logger.Warn("failed to get system user", zap.Error(err))
		return nil, fmt.Errorf("failed to get system user: %w", err)
	}
	systemUser := accountdb.ConvertUser(systemUserResp)
	systemUser.Projects = []accounttypes.Project{}
	user := &accountdb.User{
		User:           *systemUser,
		TenantID:       resp.Org.Id,
		IsImpersonated: false,
	}
	for _, project := range resp.Projects {
		user.User.Projects = append(user.User.Projects, accounttypes.Project{
			Updatable: accounttypes.Updatable{
				Base: accounttypes.Base{
					ID: project.Id,
				},
			},
		})
	}
	user.LastName = *publicKey
	user.Email = "OpenAPI_" + *publicKey
	return user, nil
}

func (c *openAPIAuth) queryPublicKey(ctx *gin.Context) *string {
	authType := ctx.GetHeader(APIAuthTypeKey)
	if authType != string(APIAuthTypeOpenAPI) {
		return nil
	}
	authContent := ctx.GetHeader(OpenAPIAKKey)
	var result kong.OpenAPIAuthResult
	err := json.Unmarshal([]byte(authContent), &result)
	if err != nil {
		log.GetGinLogger(ctx).Warn("failed to unmarshal: auth content", zap.Error(err))
		return nil
	}
	if result.PublicKey == "" {
		log.GetGinLogger(ctx).Warn("failed to retrieve public_key: auth content")
		return nil
	}
	return &result.PublicKey
}
