package account

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/sercand/kuberesolver/v5"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/client"

	accountadapter "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/adapter/account"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	grpcutil "github.com/tidbcloud/tidb-management-service/utilities/grpc"
)

type accountClientImpl struct {
	client account.AccountAPIServiceClient
}

func NewAccountClient(cfg *config.Config) accountadapter.Client {
	addr := cfg.ServerConfig.DependentSvcAddresses.AccountProviderSvcAddr
	timeoutMs := cfg.ServerConfig.DependentSvcAddresses.WaitTimeoutInMilliSec
	opts := []tigrpc.Option{
		tigrpc.WithPrometheus(),
		tigrpc.WithTracing(),
		tigrpc.WithUnaryInterceptors(
			grpcutil.ClientPayloadLogInterceptor,
			grpcutil.TimeoutInterceptor(time.Duration(timeoutMs)*time.Millisecond),
		),
	}
	if strings.HasPrefix(addr, "kubernetes://") {
		kuberesolver.RegisterInCluster()
		opts = append(opts, tigrpc.WithLoadBalance())
	}
	conn, err := tigrpc.NewClientConn(addr, opts...)
	if err != nil {
		panic(fmt.Sprintf("dial account-provider-svc: %s", err))
	}

	return accountClientImpl{
		client: account.NewAccountAPIServiceClient(conn),
	}
}

func (p accountClientImpl) GetTenant(ctx context.Context, id uint64) (*accountadapter.TenantInfo, bizerr.BizError) {
	req := &account.GetOrgByIDRequest{
		Id: id,
	}

	resp, e := p.client.GetOrgByID(ctx, req)
	if e != nil {
		return nil, bizerr.WrapGrpcError(e, "GetOrgByID")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.New(bizerr.ErrCode(resp.BaseResp.ErrCode), "%s", resp.BaseResp.ErrMsg)
	}

	return p.convertTenant(resp.Org), nil
}

func (p accountClientImpl) convertProject(project *account.Project) *accountadapter.ProjectInfo {
	return &accountadapter.ProjectInfo{
		ID:             project.Id,
		TenantID:       project.TenantId,
		Name:           project.Name,
		IsCrossAccount: project.IsCrossAccount,
		AwsCmekEnabled: project.AwsCmekEnabled,
		CreatedAt:      project.CreatedAt.AsTime(),
	}
}

func (p accountClientImpl) GetProject(ctx context.Context, id uint64) (*accountadapter.ProjectInfo, bizerr.BizError) {
	req := &account.GetProjectByIDRequest{
		Id: id,
	}
	resp, err := p.client.GetProjectByID(ctx, req)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "GetProjectByID")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.New(bizerr.ErrCode(resp.BaseResp.ErrCode), "%s", resp.BaseResp.ErrMsg)
	}

	return p.convertProject(resp.Project), nil
}

func (p accountClientImpl) convertTenant(tenant *account.Org) *accountadapter.TenantInfo {
	tenantInfo := &accountadapter.TenantInfo{
		ID:        tenant.Id,
		Name:      tenant.Name,
		TimeZone:  tenant.TimeZone,
		Plan:      tenant.Plan,
		CreatedAt: tenant.CreatedAt.AsTime(),
	}
	return tenantInfo
}

func (p accountClientImpl) GetClusterAuthPubKey(
	ctx context.Context,
	orgId, projectId, clusterId uint64,
) (string, bizerr.BizError) {
	req := &account.GetClusterAuthPubKeyReq{
		OrgId:     orgId,
		ProjectId: projectId,
		ClusterId: clusterId,
		IsDevTier: false,
	}
	resp, e := p.client.GetClusterAuthPubKey(ctx, req)
	if e != nil {
		return "", bizerr.WrapGrpcError(e, "GetClusterAuthPubKey")
	}
	if resp.BaseResp.ErrCode != 0 {
		return "", bizerr.New(bizerr.ErrCode(resp.BaseResp.ErrCode), "%s", resp.BaseResp.ErrMsg)
	}

	return resp.Jwks, nil
}

func (p accountClientImpl) QueryTenant(
	ctx context.Context,
	filter accountadapter.TenantFilter,
) (map[uint64]*accountadapter.TenantInfo, bizerr.BizError) {
	req := &account.OrgFilter{
		Ids: filter.TenantIDs,
	}
	resp, e := p.client.ListOrgs(ctx, req)
	if e != nil {
		return nil, bizerr.WrapGrpcError(e, "ListOrgs")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.New(bizerr.ErrCode(resp.BaseResp.ErrCode), "%s", resp.BaseResp.ErrMsg)
	}

	out := make(map[uint64]*accountadapter.TenantInfo, len(resp.GetOrgs()))
	for _, each := range resp.Orgs {
		out[each.Id] = p.convertTenant(each)
	}

	return out, nil
}

func (p accountClientImpl) QueryProject(
	ctx context.Context,
	filter accountadapter.ProjectFilter,
) (map[uint64]*accountadapter.ProjectInfo, bizerr.BizError) {
	req := &account.ProjectFilter{
		OrgIds: filter.TenantIDs,
		Status: filter.Statuses,
		Ids:    filter.ProjectIDs,
	}
	resp, e := p.client.ListProjects(ctx, req)
	if e != nil {
		return nil, bizerr.WrapGrpcError(e, "ListProjects")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.New(bizerr.ErrCode(resp.BaseResp.ErrCode), "%s", resp.BaseResp.ErrMsg)
	}

	out := make(map[uint64]*accountadapter.ProjectInfo, len(resp.Projects))
	for _, each := range resp.Projects {
		out[each.Id] = p.convertProject(each)
	}

	return out, nil
}

func (p accountClientImpl) GetClusterInitialDelegateUsers(
	ctx context.Context,
	tenantID, projectID uint64,
) ([]*account.DBUser, bizerr.BizError) {
	resp, e := p.client.GetDBUsers(ctx, &account.GetDBUsersReq{
		OrgId:        tenantID,
		ProjectId:    projectID,
		IsServerless: false,
	})
	if e != nil {
		return nil, bizerr.WrapGrpcError(e, "GetClusterInitialDelegateUsers")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.New(bizerr.ErrCode(resp.BaseResp.ErrCode), "%s", resp.BaseResp.ErrMsg)
	}

	return resp.GetDbUsers(), nil
}
