package auth

import (
	"context"
	"strings"

	authv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/auth/v1"
	userapiv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/core-server/user/v1"
	iamv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/core-server/iam/v1"
	userv1 "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/core-server/user/v1"
	"github.com/tidbcloud/devops/pkg/infra/clients/opscore"
	"github.com/tidbcloud/devops/pkg/infra/consts"
	"google.golang.org/grpc/metadata"
)

const (
	PingCAPOrgID uint64 = 1
	clientIPKey         = "x-client-ip"
)

type AuthParams struct {
	OpsCore *opscore.Client
}

type AuthnResult struct {
	User             *userapiv1.PrincipalProfile
	APIToken         *userv1.APIToken
	APIKey           *iamv1.APIKey
	PrincipalProfile *userapiv1.PrincipalProfile
}

type Auth struct {
	authCli authv1.AuthServiceClient
}

func NewAuth(p AuthParams) (*Auth, error) {
	return &Auth{
		authCli: p.OpsCore.Auth,
	}, nil
}

func (a *Auth) AuthnFromAccessToken(ctx context.Context, token string) (*AuthnResult, bool, error) {
	result, err := a.authenticate(ctx, &authv1.AuthenticateRequest{AccessToken: &token})
	if err == nil {
		return result, false, nil
	}
	return nil, false, err
}

func (a *Auth) AuthnFromAPIToken(ctx context.Context, token, clientIP string) (*AuthnResult, bool, error) {
	ctx = metadata.AppendToOutgoingContext(ctx, clientIPKey, clientIP)
	result, err := a.authenticate(ctx, &authv1.AuthenticateRequest{ApiToken: &token})
	return result, false, err
}

func (a *Auth) AuthnFromAPIKey(ctx context.Context, key string) (*AuthnResult, bool, error) {
	result, err := a.authenticate(ctx, &authv1.AuthenticateRequest{ApiKey: &key})
	return result, false, err
}

func (a *Auth) authenticate(ctx context.Context, req *authv1.AuthenticateRequest) (*AuthnResult, error) {
	resp, err := a.authCli.Authenticate(ctx, req)
	if err != nil {
		return nil, err
	}

	result := &AuthnResult{
		User:             resp.PrincipalProfile,
		APIToken:         resp.ApiToken,
		APIKey:           resp.ApiKey,
		PrincipalProfile: resp.PrincipalProfile,
	}
	return result, nil
}

func (a *Auth) Authz(ctx context.Context, result *AuthnResult, uri, method string) (bool, error) {
	req := &authv1.AuthorizeRequest{
		Uri:    uri,
		Method: strings.ToUpper(method),
	}
	switch result.PrincipalProfile.OperatorType {
	case consts.CtxOperatorTypeAPIKey:
		req.ApiKeyName = &result.PrincipalProfile.OperatorName
	case consts.CtxOperatorTypeAPIToken:
		req.UserEmail = &result.PrincipalProfile.UserEmail
		req.ApiTokenPrivilege = &result.APIToken.Privilege
	default:
		req.UserEmail = &result.PrincipalProfile.UserEmail
	}
	resp, err := a.authCli.Authorize(ctx, req)
	if err != nil {
		return false, err
	}
	return resp.Authorized, nil
}
