package regionalservice

import (
	"context"
	"fmt"
	"time"

	"github.com/go-resty/resty/v2"
	"github.com/pingcap/errors"
	regionalpb "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
)

type Client interface {
	// GetCluster returns the cluster information by cluster id
	GetCluster(ctx context.Context, clusterID string) (*regionalpb.Cluster, error)
	// GetAPPBoxByTenantIDAndClusterID returns the appbox information by cluster id and tenant id
	GetAPPBoxByTenantIDAndClusterID(ctx context.Context, tenantID, clusterID string) (*regionalpb.APPBox, error)
	// GetAPPBoxByTenantIDAndProjectID returns the appbox information by project id and tenant id
	GetAPPBoxByTenantIDAndProjectID(ctx context.Context, tenantID, projectID string) (*regionalpb.APPBox, error)
	// GetAPPBoxByID returns the appbox information by the id
	GetAPPBoxByID(ctx context.Context, ID string) (*regionalpb.APPBox, error)
}

var _ Client = &client{}

type client struct {
	cli          *resty.Client
	unmarshalOpt protojson.UnmarshalOptions
}

func NewRegionalServiceClient(base string) Client {
	cli := resty.New().EnableTrace().SetTimeout(time.Second * 60).SetBaseURL(base)
	return &client{
		cli: cli,
		unmarshalOpt: protojson.UnmarshalOptions{
			DiscardUnknown: true,
		},
	}
}

func (c *client) GetCluster(ctx context.Context, clusterID string) (*regionalpb.Cluster, error) {
	var cluster regionalpb.Cluster
	return &cluster, c.callAPI(ctx, "GET",
		fmt.Sprintf("/api/dedicated/v1/clusters/%s", clusterID), nil, &cluster,
	)
}

func (c *client) GetAPPBoxByTenantIDAndClusterID(ctx context.Context, tenantID, clusterID string) (*regionalpb.APPBox, error) {
	var appboxes regionalpb.ListAPPBoxesResponse
	if err := c.callAPI(ctx, "GET",
		fmt.Sprintf("/api/dedicated/v1/appboxes?clusterId=%s&tenantId=%s", clusterID, tenantID), nil, &appboxes,
	); err != nil {
		return nil, errors.Trace(err)
	}
	if len(appboxes.Appboxes) == 0 {
		return nil, errors.New("no appbox found")
	}
	return appboxes.Appboxes[0], nil
}

func (c *client) GetAPPBoxByTenantIDAndProjectID(ctx context.Context, tenantID, projectID string) (*regionalpb.APPBox, error) {
	var resp regionalpb.ListAPPBoxesResponse
	if err := c.callAPI(ctx, "GET",
		fmt.Sprintf("/api/dedicated/v1/appboxes?projectId=%s&tenantId=%s", projectID, tenantID), nil, &resp,
	); err != nil {
		return nil, errors.Trace(err)
	}
	if len(resp.Appboxes) == 0 {
		return nil, errors.New("no appbox found")
	}
	return resp.Appboxes[0], nil
}

func (c *client) GetAPPBoxByID(ctx context.Context, ID string) (*regionalpb.APPBox, error) {
	var appbox regionalpb.APPBox
	if err := c.callAPI(ctx, "GET",
		fmt.Sprintf("/api/dedicated/v1/appboxes/%s", ID), nil, &appbox,
	); err != nil {
		return nil, errors.Trace(err)
	}
	return &appbox, nil
}

//nolint:unparam
func (c *client) callAPI(
	ctx context.Context,
	method, uriPath string,
	body interface{}, dest proto.Message,
) error {
	req := c.cli.R().SetContext(ctx).SetBody(body)
	resp, err := req.Execute(method, uriPath)

	logger := tilog.WithContextL(ctx).With(
		zap.String("method", method),
		zap.String("url", uriPath),
		zap.Any("http_trace", req.TraceInfo()))
	if err != nil {
		logger.Error("http request error", zap.Any("resp", resp), zap.Error(err))
		return errors.Trace(err)
	}
	if !resp.IsSuccess() {
		logger.Warn("http request not success",
			zap.String("status", resp.Status()),
			zap.Any("resp", resp))
		return errors.New(resp.String())
	}
	logger.Info("http request OK")
	if dest == nil {
		return nil
	}
	if err := c.unmarshalOpt.Unmarshal(resp.Body(), dest); err != nil {
		logger.Error("unmarshal response error", zap.Any("resp", resp), zap.Error(err))
		return errors.Trace(err)
	}
	return nil
}
