package api

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/pingcap/errors"
)

const (
	auth0ClientIDEenKey  = "O11Y_AUTH0_CLIENT_ID"
	auth0ClientSecretEnv = "O11Y_AUTH0_CLIENT_SECRET"
)

var (
	apiTimeout = 10 * time.Second
)

type Api interface {
	// CreateAwsO11yInfra is idempotent, if the cluster with the tenant/project/cluster ID already exists,
	// it will return the existed cluster info.
	CreateAwsO11yInfra(ctx context.Context, provider, region, tenantID, projectID, clusterID string, mode ShareMode, agent AgentEnvironment) (*ObservabilityCluster, error)
	DeleteO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error)
	GetO11yInfra(ctx context.Context, provider, region, tenantID, projectID, clusterID string) ([]ClusterBaseInfo, error)
	CreateO11yInfraV2(ctx context.Context, provider, region, tenantID, projectID, clusterID, bizType, alertTempID, monitorSpec string, mode ShareMode, muteAlert bool, agent AgentEnvironment) (*ObservabilityCluster, error)
	ListO11yInfraV2(ctx context.Context, provider, region, tenantID, projectID, clusterID, bizType string) ([]ClusterBaseInfo, error)
	GetO11yInfraV2(ctx context.Context, provider, region, tenantID, projectID, clusterID, bizType string) (*ObservabilityCluster, error)
	PauseO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error)
	ResumeO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error)
	GetO11yInfraByID(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error)
}

type apiImpl struct {
	mgr *clientManager
	env string
}

// InitApi initializes the o11y client. It will override auth0 client id and client secret if they are set in env.
func InitApi(auth0GetTokenURL, clientID, clientSecret, audience, o11yOpenAPIAddress, createO11yInfraPath, deleteO11yInfraPath, getO11yInfraPath, pauseO11yInfraPath, resumeO11yInfraPath, env string) (Api, error) {
	clientIDEnv := os.Getenv(auth0ClientIDEenKey)
	if clientIDEnv != "" {
		clientID = clientIDEnv
	}
	clientSecretEnv := os.Getenv(auth0ClientSecretEnv)
	if clientSecretEnv != "" {
		clientSecret = clientSecretEnv
	}
	if clientID == "" || clientSecret == "" {
		return nil, errors.New("failed to init o11y client, empty auth0 client ID or client secret")
	}

	mgr := &clientManager{
		auth0GetTokenURL: auth0GetTokenURL,
		auth0RequestBody: Auth0RequestBody{
			Audience:     audience,
			ClientID:     clientID,
			ClientSecret: clientSecret,
			GrantType:    grantType,
		},
		o11yOpenAPIAddress:  o11yOpenAPIAddress,
		createO11yInfraPath: createO11yInfraPath,
		deleteO11yInfraPath: deleteO11yInfraPath,
		getO11yInfraPath:    getO11yInfraPath,
		pauseO11yInfraPath:  pauseO11yInfraPath,
		resumeO11yInfraPath: resumeO11yInfraPath,
		Client:              &http.Client{Timeout: apiTimeout},
	}

	if err := mgr.refreshAccessToken(context.Background()); err != nil {
		return nil, errors.Annotate(err, "failed to init o11y client")
	}

	return &apiImpl{mgr: mgr, env: env}, nil
}

// CreateAwsO11yInfra is idempotent.
func (a *apiImpl) CreateAwsO11yInfra(ctx context.Context, provider, region, tenantID, projectID, clusterID string, mode ShareMode, agent AgentEnvironment) (*ObservabilityCluster, error) {
	alertTempID := GetAlertTmplByClusterAndEnv(clusterID, a.env)
	return a.mgr.createAWSO11yInfra(ctx, provider, region, tenantID, projectID, clusterID, getBizTypeByEnv(a.env), alertTempID, getMonitorSpecIDByEnv(a.env), mode, false, agent)
}

// DeleteO11yInfra is idempotent.
func (a *apiImpl) DeleteO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error) {
	return a.mgr.deleteO11yInfra(ctx, provider, region, o11yInfraID)
}

func (a *apiImpl) GetO11yInfra(ctx context.Context, provider, region, tenantID, projectID, clusterID string) ([]ClusterBaseInfo, error) {
	return a.mgr.listO11yInfra(ctx, provider, region, tenantID, projectID, clusterID, getBizTypeByEnv(a.env))
}

// CreateO11yInfraV2 for dev-tier
func (a *apiImpl) CreateO11yInfraV2(ctx context.Context, provider, region, tenantID, projectID, clusterID, bizType, alertTempID, monitorSpec string, mode ShareMode, muteAlert bool, agent AgentEnvironment) (*ObservabilityCluster, error) {
	return a.mgr.createAWSO11yInfra(
		ctx, provider, region, tenantID, projectID, clusterID, bizType, alertTempID, monitorSpec, mode, muteAlert, agent)
}

// ListO11yInfraV2 for dev-tier
func (a *apiImpl) ListO11yInfraV2(ctx context.Context, provider, region, tenantID, projectID, clusterID, bizType string) ([]ClusterBaseInfo, error) {
	return a.mgr.listO11yInfra(ctx, provider, region, tenantID, projectID, clusterID, bizType)
}

// GetO11yInfraV2 for dev-tier
func (a *apiImpl) GetO11yInfraV2(ctx context.Context, provider, region, tenantID, projectID, clusterID, bizType string) (*ObservabilityCluster, error) {
	clusters, err := a.mgr.listO11yInfra(ctx, provider, region, tenantID, projectID, clusterID, bizType)
	if err != nil {
		return nil, err
	}

	cluster := ClusterBaseInfo{ID: ""}
	for _, c := range clusters {
		if c.ApplicationID == clusterID {
			cluster = c
			break
		}
	}

	if cluster.ID == "" {
		return nil, errors.Errorf("failed to get o11y infra, not found error")
	}

	return a.mgr.getO11yInfra(ctx, provider, region, cluster.ID)
}

// PauseO11yInfra
func (a *apiImpl) PauseO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error) {
	return a.mgr.pauseO11yInfra(ctx, provider, region, o11yInfraID)
}

// ResumeO11yInfra
func (a *apiImpl) ResumeO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error) {
	return a.mgr.resumeO11yInfra(ctx, provider, region, o11yInfraID)
}

// GetO11yInfraByID
func (a *apiImpl) GetO11yInfraByID(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error) {
	return a.mgr.getO11yInfra(ctx, provider, region, o11yInfraID)
}

func GetAlertTmplByClusterAndEnv(clusterID, env string) string {
	alertTempID := tidbAlertTempID
	if clusterID == infraClusterID {
		alertTempID = infraAlertTempID
	}
	if clusterID == dmApplicationID {
		alertTempID = dmAlertTempID
	}
	if env != "prod" {
		// we use the same alert template for dev and staging
		alertTempID = fmt.Sprintf("%s-dev", alertTempID)
	}
	return alertTempID
}
func getBizTypeByEnv(env string) string {
	switch env {
	case "dev":
		return "dev-tidbcloud"
	case "staging":
		return "staging-tidbcloud"
	default:
		return bizTypeValue
	}
}

func getMonitorSpecIDByEnv(env string) string {
	switch env {
	case "dev", "staging":
		return "spec.for.test"
	default:
		return ""
	}
}
