package account

import (
	"context"
	"time"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/pkgv2/bizerr"
	"google.golang.org/grpc"

	grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	pingcaplog "github.com/pingcap/log"
	tigrpcclient "github.com/tidbcloud/tiinf/transport/tigrpc/client"
)

type Client struct {
	callTimeout time.Duration
	account.AccountAPIServiceClient
}

// we do not enable tracing for grpc
func customizedTiGRPCOptions() []tigrpcclient.Option {
	zapOpts := grpczap.WithMessageProducer(tigrpcclient.TraceIDMessageProducer)
	opts := []tigrpcclient.Option{
		tigrpcclient.WithPrometheus(),
		tigrpcclient.WithZapLogger(pingcaplog.L(), zapOpts),
	}
	return opts
}

func New(endpoint string, opts ...Option) (*Client, error) {
	dialOptions := customizedTiGRPCOptions()
	conn, err := tigrpcclient.NewClientConn(endpoint, dialOptions...)
	if err != nil {
		return nil, err
	}
	c := &Client{
		callTimeout:             3 * time.Second,
		AccountAPIServiceClient: account.NewAccountAPIServiceClient(conn),
	}
	for _, opt := range opts {
		if err := opt(c); err != nil {
			return nil, err
		}
	}
	return c, nil
}

func (client *Client) ListUsers(ctx context.Context,
	userIds []uint64,
	opts ...grpc.CallOption,
) ([]*account.User, error) {
	if len(userIds) == 0 {
		return nil, nil
	}
	resp, err := client.AccountAPIServiceClient.ListUsers(ctx,
		&account.UserFilter{
			UserIds: userIds,
		}, opts...)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "ListUsers")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Users, nil
}

func (client *Client) GetUserByEmail(ctx context.Context, req *account.GetUserByEmailRequest) (*account.User, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.GetUserByEmail(ctx, req)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "GetUserByEmail")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}

	return resp.User, nil
}

func (client *Client) GetAuthUser(ctx context.Context, req *account.GetAuthUserRequest) (*account.GetAuthUserResponse, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.GetAuthUser(ctx, req)
	if err != nil {
		return resp, bizerr.WrapGrpcError(err, "GetAuthUser")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func (client *Client) GetUserByID(ctx context.Context, req *account.GetUserByIDRequest) (*account.GetUserByIDResponse, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.GetUserByID(ctx, req)
	if err != nil {
		return resp, bizerr.WrapGrpcError(err, "GetUserByID")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func (client *Client) GetProject(
	ctx context.Context,
	projectId uint64,
) (*account.Project, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.GetProjectByID(ctx,
		&account.GetProjectByIDRequest{
			Id: projectId,
		})
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "GetProjectByID")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Project, nil
}

func (client *Client) ListProjectsByOrgsInUser(
	ctx context.Context,
	req *account.ListProjectsByOrgsInUserRequest,
) (*account.ListProjectsByOrgsInUserResponse, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.ListProjectsByOrgsInUser(ctx, req)
	if err != nil {
		return resp, bizerr.WrapGrpcError(err, "ListProjectsByOrgsInUser")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func (client *Client) ListProjectsByIds(
	ctx context.Context,
	projectIds []uint64,
) ([]*account.Project, error) {
	result := make([]*account.Project, 0, len(projectIds))
	for start := 0; start < len(projectIds); start += 100 {
		end := start + 100
		if end > len(projectIds) {
			end = len(projectIds)
		}
		projects, err := client.listProjectsByIds(ctx, projectIds[start:end])
		if err != nil {
			return nil, err
		}
		result = append(result, projects...)
	}
	return result, nil
}

func (client *Client) listProjectsByIds(
	ctx context.Context,
	projectIds []uint64,
) ([]*account.Project, error) {
	if len(projectIds) == 0 {
		return nil, nil
	}
	resp, err := client.AccountAPIServiceClient.ListProjects(ctx, &account.ProjectFilter{
		Ids: projectIds,
	})
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "ListProjects")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Projects, nil
}

func (client *Client) ListOrgsByUser(
	ctx context.Context,
	req *account.ListOrgsByUserRequest,
) ([]*account.Org, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.ListOrgsByUser(ctx, req)
	if err != nil {
		return nil, bizerr.WrapGrpcError(err, "ListOrgsByUser")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Orgs, nil
}

func (client *Client) GetOrgByID(
	ctx context.Context,
	req *account.GetOrgByIDRequest,
) (*account.GetOrgByIDResponse, bizerr.BizError) {
	resp, err := client.AccountAPIServiceClient.GetOrgByID(ctx, req)
	if err != nil {
		return resp, bizerr.WrapGrpcError(err, "GetOrgByID")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func (client *Client) ListOrgs(
	ctx context.Context,
	in *account.OrgFilter,
	opts ...grpc.CallOption,
) (*account.ListOrgsResponse, error) {
	resp, err := client.AccountAPIServiceClient.ListOrgs(ctx, in, opts...)
	if err != nil {
		return resp, bizerr.WrapGrpcError(err, "ListOrgs")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}
