//go:generate ${GOBIN}/mockgen -destination client_mock.go -package=mgmthttpcli ${GO_MODULE}/pkg/infra/clients/mgmthttpcli MgmtHTTPClient
package mgmthttpcli

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/tidbcloud/devops/api"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/log"

	"github.com/go-resty/resty/v2"
	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
)

var (
	defaultProtojsonUnmarshalOptions = protojson.UnmarshalOptions{
		// enable this filed to enhance compatibility in where the upstream adds some new fields.
		DiscardUnknown: true,
	}
)

type MgmtHTTPClient interface {
	ClusterAgent(clusterID string) ClusterAgent
	ClusterServer(clusterID string) ClusterServer
	CdcAgentV1(clusterID string) CDCAgentV1
	CdcAgentV2(clusterID string) CDCAgentV2
	RegionServer(region, cloudProvider string) RegionServer
	NextGen() NextGenAPI
	NextGenClusterAgent(clusterID, sharedPoolID string) NextGenClusterAgent
	NextGenRegionServer(logicRegion, cloudProvider string) NextGenRegionServer
	ConfigCenterNgProxy() ConfigCenterNgProxy
}

type mgmtHTTPClient struct {
	core       *resty.Client
	coreSvcCli *coresvccli.CoreSvcCli
}

func NewMgmtHTTPClient(addr string, token string, coreSvcCli *coresvccli.CoreSvcCli) (MgmtHTTPClient, error) {
	c := &mgmtHTTPClient{
		coreSvcCli: coreSvcCli,
	}

	c.core = resty.New().
		SetTransport(otelhttp.NewTransport(http.DefaultTransport)).
		SetTimeout(time.Second * 100).
		SetBaseURL(addr).
		SetJSONMarshaler(func(obj any) ([]byte, error) {
			if objP, ok := obj.(proto.Message); ok {
				return protojson.Marshal(objP)
			}

			return json.Marshal(obj)
		}).
		SetJSONUnmarshaler(func(data []byte, obj any) error {
			if objP, ok := obj.(proto.Message); ok {
				return defaultProtojsonUnmarshalOptions.Unmarshal(data, objP)
			}

			return json.Unmarshal(data, obj)
		}).
		OnBeforeRequest(func(_ *resty.Client, r *resty.Request) error {
			r.SetAuthToken(token)
			return nil
		}).
		OnAfterResponse(func(c *resty.Client, r *resty.Response) error {
			log.FromContext(r.Request.Context()).WithName("MgmtHTTPClient").Info("called mgmt http",
				"status", r.Status(), "method", r.Request.Method, "url", r.Request.URL)

			if !r.IsSuccess() {
				err := fmt.Errorf("status: %s, body: %s", r.Status(), string(r.Body()))
				if r.StatusCode() == http.StatusNotFound {
					return errs.APIErrorNew(api.ErrCodeNotFound, err)
				}
				return err
			}
			return nil
		})

	return c, nil
}

func (c *mgmtHTTPClient) ClusterAgent(clusterID string) ClusterAgent {
	return &clusterAgent{
		core:      c.core,
		clusterID: clusterID,
	}
}

func (c *mgmtHTTPClient) ClusterServer(clusterID string) ClusterServer {
	return &clusterServer{
		core:      c.core,
		clusterID: clusterID,
	}
}

func (c *mgmtHTTPClient) RegionServer(region, cloudProvider string) RegionServer {
	return &regionServer{
		core:          c.core,
		region:        region,
		cloudProvider: cloudProvider,
	}
}

func (c *mgmtHTTPClient) NextGen() NextGenAPI {
	return &nextgen{
		core: c.core,
	}
}

func (c *mgmtHTTPClient) NextGenClusterAgent(clusterID, sharedPoolID string) NextGenClusterAgent {
	return &ngClusterAgent{
		core:         c.core,
		clusterID:    clusterID,
		sharedPoolID: sharedPoolID,
	}
}

func (c *mgmtHTTPClient) NextGenRegionServer(logicRegion, cloudProvider string) NextGenRegionServer {
	return &ngRegionServer{
		core:          c.core,
		logicRegion:   logicRegion,
		cloudProvider: cloudProvider,
	}
}

func (c *mgmtHTTPClient) CdcAgentV1(clusterID string) CDCAgentV1 {
	return &cdcAgentV1{
		core:      c.core,
		clusterID: clusterID,
	}
}

func (c *mgmtHTTPClient) CdcAgentV2(clusterID string) CDCAgentV2 {
	return &cdcAgentV2{
		core:      c.core,
		clusterID: clusterID}
}

func (c *mgmtHTTPClient) ConfigCenterNgProxy() ConfigCenterNgProxy {
	return &configCenter{
		core: c.core,
	}
}
