package o11y

import (
	"context"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/pingcap/errors"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/infra-api/pkg/k8s"
)

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

var (
	apiTimeout = 10 * time.Second
)

type Api interface {
	CreateAwsO11yInfra(ctx context.Context, agentAccessARN, agentManagerARN, provider, region, tenantID, projectID, clusterID string, mode ShareMode) (*ObservabilityCluster, error)
	DeleteO11yInfra(ctx context.Context, provider, region, o11yInfraID string) (*ObservabilityCluster, error)
	GetO11yInfra(ctx context.Context, provider, region, tenantID, projectID, clusterID string) ([]ClusterBaseInfo, error)
	InstallAgentWithHelm(manifestDirectoryPrefix, chartRelativePath, releaseName, namespace, k8sVersion string, kubeConfig, agentChartValues []byte) (opRes controllerutil.OperationResult, renderedManifest string, err error)
	CreateO11yInfraV2(ctx context.Context, agentAccessARN, agentManagerARN, provider, region, tenantID, projectID, clusterID, bizType, alertTempID string, mode ShareMode) (*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)
}

type apiImpl struct {
	mgr *clientManager
}

// 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 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,
		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}, nil
}

// CreateAwsO11yInfra is idempotent.
func (a *apiImpl) CreateAwsO11yInfra(ctx context.Context, agentAccessARN, agentManagerARN, provider, region, tenantID, projectID, clusterID string, mode ShareMode) (*ObservabilityCluster, error) {
	alertTempID := tidbAlertTempID
	if clusterID == infraClusterID {
		alertTempID = infraAlertTempID
	}
	return a.mgr.createAWSO11yInfra(ctx, agentAccessARN, agentManagerARN, provider, region, tenantID, projectID, clusterID, bizTypeValue, alertTempID, mode)
}

// 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, bizTypeValue)
}

func (a *apiImpl) InstallAgentWithHelm(manifestDirectoryPrefix, chartRelativePath, releaseName, namespace, k8sVersion string, kubeConfig, agentChartValues []byte) (opRes controllerutil.OperationResult, renderedManifest string, err error) {
	kubeConfigFilename, err := k8s.WriteTempFile(kubeConfig)
	if err != nil {
		return controllerutil.OperationResultNone, "", errors.Annotate(err, "failed to write kubeConfig to file")
	}
	defer os.Remove(kubeConfigFilename)
	chartValuesFilename, err := k8s.WriteTempFile(agentChartValues)
	if err != nil {
		return controllerutil.OperationResultNone, "", errors.Annotate(err, "failed to write chartValues to file")
	}
	defer os.Remove(chartValuesFilename)

	var (
		helm    = k8s.NewHelm().SetDir(manifestDirectoryPrefix).WithKubeConfig(kubeConfigFilename)
		kubectl = k8s.NewKubectl().SetDir(manifestDirectoryPrefix).WithKubeConfig(kubeConfigFilename)
	)

	chartPath := filepath.Join(manifestDirectoryPrefix, chartRelativePath)

	opRes, out, err := helm.Apply(
		kubectl.WithNamespace(namespace),
		releaseName,
		chartPath,
		"--namespace", namespace,
		"--kube-version", k8sVersion,
		"--values", chartValuesFilename,
	)
	if err != nil {
		return controllerutil.OperationResultNone, out, errors.Annotatef(err, "failed to install agent helm release, output: %s", out)
	}
	return opRes, out, nil
}

// CreateO11yInfraV2
func (a *apiImpl) CreateO11yInfraV2(ctx context.Context, agentAccessARN, agentManagerARN, provider, region, tenantID, projectID, clusterID, bizType, alertTempID string, mode ShareMode) (*ObservabilityCluster, error) {
	return a.mgr.createAWSO11yInfra(ctx, agentAccessARN, agentManagerARN, provider, region, tenantID, projectID, clusterID, bizType, alertTempID, mode)
}

// ListO11yInfraV2
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
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)
}
