package o11ycluster

import (
	"context"
	"os"

	"github.com/go-logr/logr"
	"github.com/prometheus/client_golang/prometheus"
	api "github.com/tidbcloud/infra-provider/pkg/compat/k8s/resources/o11ycluster/api"

	pkgconfig "github.com/tidbcloud/infra-provider/pkg/config"
)

type method string

const (
	createMethod method = "create"
	getMethod    method = "get"
	deleteMethod method = "delete"
	pauseMethod  method = "pause"
	resumeMethod method = "resume"

	auth0ClientID     = "O11Y_AUTH0_CLIENT_ID"
	auth0ClientSecret = "O11Y_AUTH0_CLIENT_SECRET"
)

type O11yInfraAPI interface {
	Create(Params) (*api.ObservabilityCluster, error)
	Delete(Params) (*api.ObservabilityCluster, error)
	List(Params) ([]api.ClusterBaseInfo, error)
	Pause(Params) (*api.ObservabilityCluster, error)
	Resume(Params) (*api.ObservabilityCluster, error)
	GetByID(Params) (*api.ObservabilityCluster, error)
}

type Params struct {
	ctx         context.Context
	provider    string
	region      string
	tenantID    string
	projectID   string
	clusterID   string
	o11yInfraID string
	mode        api.ShareMode
	// ignore following fields when call V1 interface method
	bizType     string
	alertTempID string
	monitorSpec string
	muteAlert   bool
	agent       api.AgentEnvironment
}

func CreateParams(ctx context.Context, provider, region, tenantID, projectID, clusterID string, mode api.ShareMode, agent api.AgentEnvironment) Params {
	return Params{
		ctx:       ctx,
		provider:  provider,
		region:    region,
		tenantID:  tenantID,
		projectID: projectID,
		clusterID: clusterID,
		mode:      mode,
		agent:     agent,
	}
}

func CreateParamsV2(ctx context.Context, provider, region, tenantID,
	projectID, clusterID, bizType, alertTempID string, muteAlert bool, mode api.ShareMode, agent api.AgentEnvironment) Params {
	return Params{
		ctx:         ctx,
		provider:    provider,
		region:      region,
		tenantID:    tenantID,
		projectID:   projectID,
		clusterID:   clusterID,
		mode:        mode,
		bizType:     bizType,
		alertTempID: alertTempID,
		muteAlert:   muteAlert,
		agent:       agent,
	}
}

func GetParams(ctx context.Context, provider, region, tenantID, projectID, clusterID string) Params {
	return Params{
		ctx:       ctx,
		provider:  provider,
		region:    region,
		tenantID:  tenantID,
		projectID: projectID,
		clusterID: clusterID,
	}
}

func GetParamsV2(ctx context.Context, provider, region, tenantID, projectID, clusterID,
	bizType string) Params {
	return Params{
		ctx:       ctx,
		provider:  provider,
		region:    region,
		tenantID:  tenantID,
		projectID: projectID,
		clusterID: clusterID,
		bizType:   bizType,
	}
}

func DeleteParams(ctx context.Context, provider, region, o11yInfraID string) Params {
	return Params{
		ctx:         ctx,
		provider:    provider,
		region:      region,
		o11yInfraID: o11yInfraID,
	}
}

func PauseParams(ctx context.Context, provider, region, o11yInfraID string) Params {
	return Params{
		ctx:         ctx,
		provider:    provider,
		region:      region,
		o11yInfraID: o11yInfraID,
	}
}

func ResumeParams(ctx context.Context, provider, region, o11yInfraID string) Params {
	return Params{
		ctx:         ctx,
		provider:    provider,
		region:      region,
		o11yInfraID: o11yInfraID,
	}
}

func GetByIDParams(ctx context.Context, provider, region, o11yInfraID string) Params {
	return Params{
		ctx:         ctx,
		provider:    provider,
		region:      region,
		o11yInfraID: o11yInfraID,
	}
}

type O11yInfraAPIClient struct {
	client  api.Api
	counter *prometheus.CounterVec
}

var _ O11yInfraAPI = &O11yInfraAPIClient{}

// NewO11yInfraAPIClient initializes the o11y infra API client and return it when two required environment variables are set.
// Otherwise, it will return nil, which avoids the leakage of monitoring resources in the personal dev environment.
func NewO11yInfraAPIClient(config pkgconfig.ObservabilityInfraConfig, logger logr.Logger, counter *prometheus.CounterVec) *O11yInfraAPIClient {
	_, ok1 := os.LookupEnv(auth0ClientID)
	_, ok2 := os.LookupEnv(auth0ClientSecret)

	if ok1 && ok2 {
		client, err := api.InitApi(
			config.Auth0GetTokenURL,
			config.Auth0ClientID,
			config.Auth0ClientSecret,
			config.Auth0Audience,
			config.OpenAPIAddress,
			config.CreateO11yInfraPath,
			config.DeleteO11yInfraPath,
			config.GetO11yInfraPath,
			config.PauseO11yInfraPath,
			config.ResumeO11yInfraPath,
			config.Env,
		)
		if err != nil {
			logger.Error(err, "unable to init o11y infra api client")
			os.Exit(1)
		}
		return &O11yInfraAPIClient{
			client:  client,
			counter: counter,
		}
	}

	logger.Info("o11y auth config is not set correctly, will disable o11y agents")
	return nil
}

func (api *O11yInfraAPIClient) Create(p Params) (_ *api.ObservabilityCluster, err error) {
	defer func() { api.recordRequest(err, p, createMethod) }()
	return api.client.CreateAwsO11yInfra(p.ctx, p.provider, p.region, p.tenantID, p.projectID, p.clusterID, p.mode, p.agent)
}

func (api *O11yInfraAPIClient) Delete(p Params) (_ *api.ObservabilityCluster, err error) {
	defer func() { api.recordRequest(err, p, deleteMethod) }()
	return api.client.DeleteO11yInfra(p.ctx, p.provider, p.region, p.o11yInfraID)
}

// [Deprecated]
func (api *O11yInfraAPIClient) Get(p Params) ([]api.ClusterBaseInfo, error) {
	return api.List(p)
}

func (api *O11yInfraAPIClient) List(p Params) (_ []api.ClusterBaseInfo, err error) {
	defer func() { api.recordRequest(err, p, getMethod) }()
	return api.client.GetO11yInfra(p.ctx, p.provider, p.region, p.tenantID, p.projectID, p.clusterID)
}

func (api *O11yInfraAPIClient) GetByID(p Params) (_ *api.ObservabilityCluster, err error) {
	defer func() { api.recordRequest(err, p, getMethod) }()
	return api.client.GetO11yInfraByID(p.ctx, p.provider, p.region, p.o11yInfraID)
}

func (api *O11yInfraAPIClient) Pause(p Params) (_ *api.ObservabilityCluster, err error) {
	defer func() { api.recordRequest(err, p, pauseMethod) }()
	return api.client.PauseO11yInfra(p.ctx, p.provider, p.region, p.o11yInfraID)
}

func (api *O11yInfraAPIClient) Resume(p Params) (_ *api.ObservabilityCluster, err error) {
	defer func() { api.recordRequest(err, p, resumeMethod) }()
	return api.client.ResumeO11yInfra(p.ctx, p.provider, p.region, p.o11yInfraID)
}

// recordRequest records the every request result to prometheus.
func (api *O11yInfraAPIClient) recordRequest(err error, p Params, m method) {
	if api.counter == nil {
		return
	}

	labelValues := []string{p.region, p.tenantID, p.projectID, p.clusterID, p.o11yInfraID, string(m)}
	if err != nil {
		labelValues = append(labelValues, err.Error())
	} else {
		labelValues = append(labelValues, "success")
	}

	api.counter.WithLabelValues(labelValues...).Inc()
}

type O11yInfraAPIClientV2 struct {
	*O11yInfraAPIClient
}

func NewO11yInfraAPIClientV2(config pkgconfig.ObservabilityInfraConfig, logger logr.Logger, counter *prometheus.CounterVec) *O11yInfraAPIClientV2 {
	return &O11yInfraAPIClientV2{
		O11yInfraAPIClient: NewO11yInfraAPIClient(config, logger, counter),
	}
}

func (api *O11yInfraAPIClientV2) Create(p Params) (_ *api.ObservabilityCluster, err error) {
	defer func() { api.recordRequest(err, p, createMethod) }()
	return api.client.CreateO11yInfraV2(p.ctx,
		p.provider,
		p.region,
		p.tenantID,
		p.projectID,
		p.clusterID,
		p.bizType,
		p.alertTempID,
		p.monitorSpec,
		p.mode,
		p.muteAlert,
		p.agent)
}

func (api *O11yInfraAPIClientV2) List(p Params) (_ []api.ClusterBaseInfo, err error) {
	defer func() { api.recordRequest(err, p, getMethod) }()
	return api.client.ListO11yInfraV2(p.ctx,
		p.provider,
		p.region,
		p.tenantID,
		p.projectID,
		p.clusterID,
		p.bizType)
}
