package account

import (
	"context"
	"sync"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/ecosystem-service/config"
	"github.com/tidbcloud/ecosystem-service/internal/service/grpc/account/client"
	"github.com/tidbcloud/ecosystem-service/internal/utils"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.opentelemetry.io/otel"
)

var Tracer = otel.Tracer("grpc-account")

type AccountProviderSvc interface {
	GetAuthUser(ctx context.Context, email string, orgID uint64, skipCheckOrg bool) (*account.GetAuthUserResponse, error)
	ListOrgsByUser(ctx context.Context, userID uint64) ([]*account.Org, error)
	ListProjectsByOrgsInUser(ctx context.Context, userID uint64, orgIDs []uint64) ([]*account.OrgProject, error)
	ListOrgs(ctx context.Context, orgIDs []uint64) ([]*account.Org, error)
	ListProjects(ctx context.Context, projectIDs []uint64) ([]*account.Project, error)
}

var (
	coreSvcOnce        sync.Once
	accountProviderSvc *accountProviderSvcImpl
)

type accountProviderSvcImpl struct {
	client account.AccountAPIServiceClient
}

func NewAccountProviderSvc(conf *config.ConfigWrapper) AccountProviderSvc {
	coreSvcOnce.Do(func() {
		accountProviderSvc = &accountProviderSvcImpl{
			client: client.GetAccountProviderClient(conf),
		}
	})
	return accountProviderSvc
}

func (svc *accountProviderSvcImpl) GetAuthUser(ctx context.Context, email string, orgID uint64, skipCheckOrg bool) (_ *account.GetAuthUserResponse, err error) {
	ctx, span := Tracer.Start(ctx, "accountProviderSvcImpl.GetAuthUser")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	var req *account.GetAuthUserRequest
	if skipCheckOrg {
		req = &account.GetAuthUserRequest{
			UserEmail:    email,
			SkipCheckOrg: true,
		}
	} else {
		req = &account.GetAuthUserRequest{
			UserEmail: email,
			OrgId:     orgID,
		}
	}
	resp, err := svc.client.GetAuthUser(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func (svc *accountProviderSvcImpl) ListOrgsByUser(ctx context.Context, userID uint64) (_ []*account.Org, err error) {
	ctx, span := Tracer.Start(ctx, "accountProviderSvcImpl.ListOrgsByUser")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	req := &account.ListOrgsByUserRequest{
		UserId: userID,
	}
	resp, err := svc.client.ListOrgsByUser(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Orgs, nil
}

func (svc *accountProviderSvcImpl) ListProjectsByOrgsInUser(ctx context.Context, userID uint64, orgIDs []uint64) (_ []*account.OrgProject, err error) {
	ctx, span := Tracer.Start(ctx, "accountProviderSvcImpl.ListProjectsByOrgsInUser")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	req := &account.ListProjectsByOrgsInUserRequest{
		UserId: userID,
		OrgIds: orgIDs,
	}
	resp, err := svc.client.ListProjectsByOrgsInUser(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Projects, nil
}

func (svc *accountProviderSvcImpl) ListOrgs(ctx context.Context, orgIDs []uint64) (_ []*account.Org, err error) {
	ctx, span := Tracer.Start(ctx, "accountProviderSvcImpl.ListOrgs")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	if len(orgIDs) == 0 {
		tilog.Warn(ctx, "ListOrgs: orgIDs is empty")
		return []*account.Org{}, nil
	}

	req := &account.OrgFilter{
		Ids: orgIDs,
	}
	resp, err := svc.client.ListOrgs(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Orgs, nil
}

func (svc *accountProviderSvcImpl) ListProjects(ctx context.Context, projectIDs []uint64) (_ []*account.Project, err error) {
	ctx, span := Tracer.Start(ctx, "accountProviderSvcImpl.ListProjects")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	if len(projectIDs) == 0 {
		tilog.Warn(ctx, "ListProjects: ProjectIDs is empty")
		return []*account.Project{}, nil
	}

	req := &account.ProjectFilter{
		Ids: projectIDs,
	}
	resp, err := svc.client.ListProjects(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.Projects, nil
}
