package auth

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/tidbcloud/account/idl/pbgen/proto/acl"
	"github.com/tidbcloud/core-portal/internal/central/grpc/grpcli"
	pkgHttp "github.com/tidbcloud/core-portal/internal/central/pkg/grpc/http"
	entity "github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/pkgv2/auth/kong"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
)

func getOpenAPIPublicKey(ctx context.Context) (string, error) {
	authType := pkgHttp.FirstHeader(ctx, "X-Auth-Type")
	if authType != string(kong.AuthTypeOpenAPI) {
		return "", fmt.Errorf("unexpected auth type: %s", authType)
	}

	authContent := pkgHttp.FirstHeader(ctx, "X-Auth-Content")
	result := new(kong.OpenAPIAuthResult)
	err := json.Unmarshal([]byte(authContent), result)
	if err != nil {
		return "", fmt.Errorf("failed to unmarshal: auth content: %s, error: %s", authContent, err)
	}
	publicKey := result.PublicKey
	if publicKey == "" {
		return "", fmt.Errorf("failed to retrieve public_key: auth content: %s", authContent)
	}
	return publicKey, nil
}

func getApiKeyUser(ctx context.Context) (*User, error) {
	publicKey, err := getOpenAPIPublicKey(ctx)
	if err != nil {
		log.Error(ctx, "failed to get open api public key", zap.Error(err))
		return nil, err
	}

	log.Info(ctx, "request to account-acl-svc GetOpenAPIAccess API", zap.String("publicKey", publicKey))
	getOpenAPIAccessReq := &acl.GetOpenAPIAccessReq{
		PublicKey: publicKey,
	}
	resp, err := grpcli.GetOpenAPIAccess(ctx, getOpenAPIAccessReq)
	if err != nil {
		log.Error(ctx, "failed to call account-acl-svc GetOpenAPIAccess API", zap.String("publicKey", publicKey), zap.Error(err))
		return nil, err
	}

	if resp.Org == nil {
		log.Error(ctx, "failed to get org id of public key", zap.String("publicKey", publicKey))
		return nil, fmt.Errorf("failed to retrieve tenantID: public key: %s", publicKey)
	}
	if len(resp.Projects) == 0 {
		log.Error(ctx, "failed to get projects of public key", zap.String("publicKey", publicKey))
		return nil, fmt.Errorf("failed to retrieve projectID: public key: %s", publicKey)
	}

	orgID := resp.Org.Id
	user, err := GetOrCreateSystemUser(ctx, orgID)
	if err != nil {
		log.Error(ctx, "failed to get system user", zap.String("publicKey", publicKey), zap.Uint64("tenantID", orgID), zap.Error(err))
		return nil, fmt.Errorf("failed to get api key user: public key: %s", publicKey)
	}

	for _, project := range resp.Projects {
		user.Projects = append(user.Projects, Project{
			ID: entity.ID(project.Id),
		})
	}
	return user, nil
}
