package integration

import (
	"context"
	"errors"
	"fmt"

	"github.com/DataDog/datadog-api-client-go/v2/api/datadog"
	"github.com/DataDog/datadog-api-client-go/v2/api/datadogV1"
	"github.com/ngaut/log"
	"go.uber.org/zap"

	"github.com/shiningrush/fastflow/pkg/mod"

	"net/http"
	"strings"

	"github.com/tidbcloud/metrics-integration/global/internal/domain/entity"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/account"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/di"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/grpc/grpcli"
	"github.com/tidbcloud/metrics-integration/global/pkg/cvt"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"gorm.io/gorm"
)

// CreateDatadogIntegration enables the datadog integration for a given customer project.
// @param payload - types of data that Datadog will collect.
func CreateDatadogIntegration(ctx context.Context, clusterId entity.ID, site entity.DatadogSite, apiKey string, creator string) (entity.Integration, error) {
	var err error
	// auditlog
	defer func() {
		details := auditlog.Details{
			auditlog.KeyDescription:     "a metrics integration is created",
			auditlog.KeyIntegrationType: "datadog",
			auditlog.KeyError:           err,
		}
		user := account.GetUserFromContext(ctx)
		eventType := auditlog.EventCreateMetricsIntegration
		details[auditlog.KeyApiKey] = MaskDatadogAPIKey(apiKey)
		details[auditlog.KeySite] = string(site)
		eventSubmitErr := auditlog.NewEvent(ctx, eventType).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(user.TenantID).Submit()
		if eventSubmitErr != nil {
			log.Error(ctx, "CreateIntegration submit event fail",
				zap.Uint64("tenantID", user.TenantID),
				zap.Uint64("userID", user.ID),
				zap.Error(eventSubmitErr))
		}
	}()

	// get infos from cluster service
	cluster, err := grpcli.GetClusterByID(context.Background(), uint64(clusterId))
	if err != nil {
		return entity.Integration{
			ProviderType: entity.IntegrationTypeDatadog,
		}, fmt.Errorf("failed to get cluster info: %w", err)
	}
	region := cluster.Region
	cloudProviderType, err := normalizeIntegrationCloudProviderType(cluster.CloudProvider)
	if err != nil {
		return entity.Integration{
			ProviderType: entity.IntegrationTypeDatadog,
		}, err
	}

	// We'll create (or reuse) the GeneralIntegration and the Integration in a single
	// DB transaction so the generated global ID from GeneralIntegration is used as
	// the Integration.ID and the whole operation is atomic.
	maskedKey := MaskDatadogAPIKey(apiKey)
	datadogIntegration := entity.Integration{
		TenantID:            entity.ID(cluster.OrganizationId),
		ProjectID:           entity.ID(cluster.ProjectId),
		ClusterID:           clusterId,
		Region:              region,
		CloudProviderType:   cloudProviderType,
		ProviderType:        entity.IntegrationTypeDatadog,
		Status:              entity.IntegrationStatusActive,
		StatusReason:        "",
		Payloads:            entity.IntegrationPayloads{Payloads: []entity.IntegrationPayload{entity.IntegrationPayloadMetrics}},
		DatadogSite:         site,
		MaskedDatadogApiKey: maskedKey,
	}

	// Encrypt the api key with AWS KMS (with the customized master key) before persisting。
	if err := di.DiCall(func(encryptor encrypt.Encryptor) error {
		encryptedKey, err := encrypt.EncryptBase64(encryptor, encrypt.Secret(apiKey))
		if err != nil {
			return fmt.Errorf("failed to encrypt Datadog API key: %w", err)
		}
		datadogIntegration.EncryptedDatadogApiKey = encryptedKey
		return nil
	}); err != nil {
		return entity.Integration{}, fmt.Errorf("failed to encrypt Datadog API key: %w", err)
	}

	// Create or update the general integration and the specific integration in one transaction
	if err := di.DiCall(func(db *gorm.DB) error {
		err := db.Transaction(func(tx *gorm.DB) error {
			// 1) Check whether a GeneralIntegration or Integration already exists for this cluster+provider
			// If any exists, reject creation: only one integration of a given type is allowed per cluster.
			var gen entity.GeneralIntegration
			if err := tx.Where("cluster_id = ? AND provider_type = ?", clusterId, entity.IntegrationTypeDatadog).First(&gen).Error; err == nil {
				return fmt.Errorf("an integration of this type already exists for the cluster")
			} else if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}

			var existing entity.Integration
			if err := tx.Where("cluster_id = ? AND provider_type = ?", clusterId, entity.IntegrationTypeDatadog).First(&existing).Error; err == nil {
				return fmt.Errorf("an integration of this type already exists for the cluster")
			} else if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}

			// 2) Create new GeneralIntegration (since none exists)
			genPtr := &entity.GeneralIntegration{
				TenantID:     entity.ID(cluster.OrganizationId),
				ProjectID:    entity.ID(cluster.ProjectId),
				ClusterID:    entity.ID(clusterId),
				ProviderType: entity.IntegrationTypeDatadog,
				Creator:      creator,
			}
			if err := tx.Create(genPtr).Error; err != nil {
				return fmt.Errorf("create general integration failed: %w", err)
			}

			// Use genPtr.ID as the Integration ID (global id across services)
			datadogIntegration.Updatable = entity.Updatable{Base: entity.Base{ID: genPtr.ID}}

			// 3) Create the specific Integration record
			if err := tx.Create(&datadogIntegration).Error; err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			return err
		}

		// workflow: create-pipeline
		if _, err := mod.GetCommander().RunDagWithTags("create-pipeline", map[string]string{
			"id": fmt.Sprintf("%d", datadogIntegration.ID),
		}, map[string]string{}); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return entity.Integration{}, err
	}

	return datadogIntegration, nil
}

// ValidateDatadogAPIKey validates the API key through Datadog site.
func ValidateDatadogAPIKey(_ context.Context, apiKey string, site entity.DatadogSite) (valid bool, err error) {
	ddCTX := context.Background()
	ddCTX = context.WithValue(
		ddCTX,
		datadog.ContextServerVariables,
		map[string]string{"site": cvt.GetDatadogSiteUrl(site)},
	)
	ddCTX = context.WithValue(
		ddCTX,
		datadog.ContextAPIKeys,
		map[string]datadog.APIKey{
			"apiKeyAuth": {Key: apiKey}},
	)
	datadogAPIClient := datadog.NewAPIClient(datadog.NewConfiguration())
	resp, r, err := datadogV1.NewAuthenticationApi(datadogAPIClient).Validate(ddCTX)
	if err != nil {
		if r == nil {
			return false, fmt.Errorf("datadog's API service is temporarily unavailable, please try again later")
		}
		switch r.StatusCode {
		case http.StatusForbidden:
			return false, fmt.Errorf("please check the key or region")
		case http.StatusInternalServerError:
			return false, fmt.Errorf("datadog's API service is temporarily unavailable, please try again later")
		default:
			return false, err
		}
	}
	return resp.GetValid(), nil
}

// MaskDatadogAPIKey masks the prefix of an API key and only keeps the last 1/8 characters.
func MaskDatadogAPIKey(k string) string {
	l := len(k)
	maskLen := l / 8
	var b strings.Builder
	b.Grow(l)
	for i := 0; i < (l-maskLen)/2; i++ {
		b.WriteString("·")
	}
	b.WriteString(k[l-maskLen:])
	return b.String()
}
