package account_sdk

import (
	"context"
	"errors"
	"time"

	"github.com/gogo/status"
	grpcprom "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/pingcap/log"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	dbaasbizerr "github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/backoff"
	"google.golang.org/grpc/credentials/insecure"

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

	apierr "github.com/pingcap-inc/clinic/pkg/api/error"
)

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

func customizedDailOptions() []grpc.DialOption {
	// no idea how to set the backoff, following is used by TiKV grpc client.
	connParams := grpc.ConnectParams{
		Backoff: backoff.Config{
			BaseDelay:  100 * time.Millisecond, // Default was 1s.
			Multiplier: 1.6,                    // Default
			Jitter:     0.2,                    // Default
			MaxDelay:   3 * time.Second,        // Default was 120s.
		},
		MinConnectTimeout: 5 * time.Second,
	}
	// dial option
	grpcprom.EnableClientHandlingTimeHistogram()
	gopts := []grpc.DialOption{
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		grpc.WithConnectParams(connParams),
		grpc.WithChainUnaryInterceptor(grpcprom.UnaryClientInterceptor),
	}
	// call option
	gopts = append(gopts, grpc.WithDefaultCallOptions(
		grpc.MaxCallRecvMsgSize(16<<20),
		grpc.MaxCallSendMsgSize(16<<20),
	))
	return gopts
}

// 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),
		tigrpcclient.WithGrpcOption(grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128 * 1024 * 1024))),
	}
	return opts
}

func New(endpoint string, opts ...Option) (*Client, error) {
	// ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	// defer cancel()

	// dialOptions := customizedDailOptions()
	// conn, err := grpc.DialContext(ctx, endpoint, dialOptions...)
	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 (c *Client) CheckToken(ctx context.Context, token string) (*account.User, error) {
	if c.callTimeout != 0 {
		tctx, cancel := context.WithTimeout(ctx, c.callTimeout)
		defer cancel()
		ctx = tctx
	}
	in := new(account.GetUserByTokenRequest)
	in.Token = token
	out, err := c.AccountAPIServiceClient.GetUserByToken(ctx, in)
	if err != nil {
		if st, ok := status.FromError(err); ok {
			log.Error("call grpc failed", zap.String("code", st.Code().String()), zap.String("message", st.Message()))
		} else {
			log.Error("call grpc failed", zap.Error(err))
		}
		return nil, err
	}
	// https://github.com/tidbcloud/pkgv2/blob/main/bizerr/code.go#L35
	if out.GetBaseResp().GetErrCode() != 0 {
		log.Error("biz code not zero", zap.Int64("err_code", out.GetBaseResp().GetErrCode()), zap.String("err_msg", out.GetBaseResp().GetErrMsg()))
		if out.GetBaseResp().GetErrCode() == int64(dbaasbizerr.ErrCodeRecordNotFound) {
			return nil, apierr.ErrBadRequest.New("user not found")
		}
		if out.GetBaseResp().GetErrCode() == int64(dbaasbizerr.ErrCodeAuth) {
			return nil, apierr.ErrUnauthorized.New("invalid user")
		}
		return nil, errors.New(out.GetBaseResp().String())
	}
	return out.GetUser(), nil
}
