package account

import (
	"context"
	"fmt"
	"sync"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/aitestsuit/configs"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/client"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

var (
	accountInitOnce sync.Once
	accountSvc      AccountSvcIface
)

type AccountSvcIface interface {
	GetAccountServiceClient() account.AccountAPIServiceClient
	GetUserByToken(ctx context.Context, token string) (*account.GetUserByTokenResponse, error)
	GetOrgByID(ctx context.Context, orgID uint64) (*account.GetOrgByIDResponse, error)
	GetProjectByID(ctx context.Context, projectID uint64) (*account.GetProjectByIDResponse, error)
}

func NewAccountSvc(ctx context.Context, cfg *configs.Config) AccountSvcIface {
	accountInitOnce.Do(func() {
		var conn *grpc.ClientConn
		conn, err := tigrpc.NewClientConn(cfg.AccountSvcAddr, tigrpc.WithDefault())
		tilog.Info(ctx, "Initializing account service client", zap.Any("err", err), zap.String("addr", cfg.AccountSvcAddr))
		if err != nil {
			panic(fmt.Sprintf("dial account-svc: %s", err))
		}

		accountClient := account.NewAccountAPIServiceClient(conn)
		accountSvc = &accountSvcImpl{accountClient}
	})

	return accountSvc
}

type accountSvcImpl struct {
	accountClient account.AccountAPIServiceClient
}

func parseAccountResp(ctx context.Context, baseResp *base.BaseResp) error {
	if baseResp == nil {
		return nil
	}

	if baseResp.ErrCode == errs.ErrCodeOK {
		return nil
	}

	if baseResp.ErrCode == errs.ErrCodeNotFound {
		return errs.NewNotFound("not found", nil)
	}

	if baseResp.ErrCode == errs.ErrCodeForbidden {
		return errs.NewForbidden("not allowed", nil)
	}

	if baseResp.ErrCode == errs.ErrCodeConflict {
		return errs.NewConflict("conflict", nil)
	}

	tilog.Info(ctx, "parse account resp", zap.Any("baseResp", baseResp))
	return errs.NewInternalServerError("account server error", nil)
}

func (a *accountSvcImpl) GetAccountServiceClient() account.AccountAPIServiceClient {
	return a.accountClient
}

func (a *accountSvcImpl) GetUserByToken(ctx context.Context, token string) (*account.GetUserByTokenResponse, error) {
	resp, err := a.accountClient.GetUserByToken(ctx, &account.GetUserByTokenRequest{Token: token})
	if err != nil {
		tilog.Error(ctx, "failed to get user by token", zap.Error(err), zap.String("token", token))
		return resp, errs.NewInternalGRPC("GetUserByToken", err)
	}

	return resp, parseAccountResp(ctx, resp.BaseResp)
}

func (a *accountSvcImpl) GetOrgByID(ctx context.Context, orgID uint64) (*account.GetOrgByIDResponse, error) {
	resp, err := a.accountClient.GetOrgByID(ctx, &account.GetOrgByIDRequest{Id: orgID})
	if err != nil {
		tilog.Error(ctx, "failed to get org by id", zap.Error(err), zap.Uint64("orgID", orgID))
		return resp, errs.NewInternalGRPC("GetOrgByID", err)
	}

	return resp, parseAccountResp(ctx, resp.BaseResp)
}

func (a *accountSvcImpl) GetProjectByID(ctx context.Context, projectID uint64) (*account.GetProjectByIDResponse, error) {
	resp, err := a.accountClient.GetProjectByID(ctx, &account.GetProjectByIDRequest{Id: projectID})
	if err != nil {
		tilog.Error(ctx, "failed to get project by id", zap.Error(err), zap.Uint64("projectID", projectID))
		return resp, errs.NewInternalGRPC("GetProjectByID", err)
	}

	return resp, parseAccountResp(ctx, resp.BaseResp)
}
