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/tidbcloud/metrics-server/central/db"
	"github.com/tidbcloud/metrics-server/central/db/entity"
	"github.com/tidbcloud/metrics-server/idl/pbgen/proto/central"
	"github.com/tidbcloud/pkgv2/secret/encrypt"
	"gorm.io/gorm"
	"net/http"
	"strings"
)

// CreateOrEnableDatadogIntegration enables the datadog integration for a given customer project.
// @param payload - types of data that Datadog will collect.
func (m Model) CreateOrEnableDatadogIntegration(ctx context.Context, projectId entity.ID, payloads []central.IntegrationPayload, site central.DatadogSite, apiKey string) (status central.IntegrationStatus, maskedKey string, err error) {
	// Map api models to db entity models.
	payloadsDB := make([]entity.IntegrationPayload, 0, len(payloads))
	for _, p := range payloads {
		payloadsDB = append(payloadsDB, payloadRestToDB(p))
	}
	siteDB := DatadogSiteRestToDB(site)
	pid := projectId
	maskedKey = MaskDatadogAPIKey(apiKey)
	datadogIntegration := entity.Integration{
		ProjectID:           pid,
		ProviderType:        entity.IntegrationTypeDatadog,
		Status:              entity.IntegrationStatusActive,
		StatusReason:        "",
		Payloads:            entity.IntegrationPayloads{Payloads: payloadsDB},
		DatadogSite:         siteDB,
		MaskedDatadogApiKey: maskedKey,
	}

	// Encrypt the api key with AWS KMS (with the customized master key).
	encryptedKey, _ := encrypt.EncryptBase64(m.encryptor, encrypt.Secret(apiKey))
	datadogIntegration.EncryptedDatadogApiKey = encryptedKey

	// Create or update the record in metadb.
	err = db.Transaction(func(tx *gorm.DB) error {
		i := new(entity.Integration)
		if err := entity.NewIntegrationQuerySet(tx).ProjectIDEq(pid).ProviderTypeEq(entity.IntegrationTypeDatadog).One(i); err != nil {
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}

			if err := datadogIntegration.Create(tx); err != nil {
				return err
			}

			return nil
		}

		datadogIntegration.ID = i.ID
		err := datadogIntegration.Update(
			tx,
			entity.IntegrationDBSchema.Status,
			entity.IntegrationDBSchema.StatusReason,
			entity.IntegrationDBSchema.Payloads,
			entity.IntegrationDBSchema.DatadogSite,
			entity.IntegrationDBSchema.MaskedDatadogApiKey,
			entity.IntegrationDBSchema.EncryptedDatadogApiKey,
		)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return central.IntegrationStatus_INTEGRATION_STATUS_UNSPECIFIED, maskedKey, err
	}

	return central.IntegrationStatus_INTEGRATION_STATUS_UNSPECIFIED, maskedKey, nil
}

// DisableDatadogIntegration disable the datadog integration for a given customer project.
func (m Model) DisableDatadogIntegration(ctx context.Context, projectId entity.ID) (central.IntegrationStatus, error) {
	err := db.Transaction(func(tx *gorm.DB) error {
		err := entity.NewIntegrationQuerySet(tx).ProjectIDEq(projectId).ProviderTypeEq(entity.IntegrationTypeDatadog).GetUpdater().SetStatus(entity.IntegrationStatusNone).Update()
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return central.IntegrationStatus_INTEGRATION_STATUS_UNSPECIFIED, err
	}
	return central.IntegrationStatus_INTEGRATION_STATUS_NONE, nil
}

// ValidateDatadogAPIKey validates the API key through Datadog site.
func (m Model) ValidateDatadogAPIKey(_ context.Context, apiKey string, site central.DatadogSite) (valid bool, err error) {
	ddCTX := context.Background()
	ddCTX = context.WithValue(
		ddCTX,
		datadog.ContextServerVariables,
		map[string]string{"site": GetDatadogSiteUrl(site)},
	)
	ddCTX = context.WithValue(
		ddCTX,
		datadog.ContextAPIKeys,
		map[string]datadog.APIKey{
			"apiKeyAuth": {Key: apiKey}},
	)
	resp, r, err := datadogV1.NewAuthenticationApi(m.datadogApi).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
}

func GetDatadogSiteUrl(site central.DatadogSite) string {
	// DatadogSiteUrl refers to [https://github.com/DataDog/datadog-agent/blob/main/pkg/config/config_template.yaml]
	switch site {
	case central.DatadogSite_DATADOG_SITE_US1:
		return "datadoghq.com"
	case central.DatadogSite_DATADOG_SITE_US3:
		return "us3.datadoghq.com"
	case central.DatadogSite_DATADOG_SITE_US5:
		return "us5.datadoghq.com"
	case central.DatadogSite_DATADOG_SITE_EU1:
		return "datadoghq.eu"
	case central.DatadogSite_DATADOG_SITE_US1_FED:
		return "ddog-gov.com"
	case central.DatadogSite_DATADOG_SITE_AP1:
		return "ap1.datadoghq.com"
	default:
		return "datadoghq.com"
	}
}

// GetDatadogDashboardUrl hardcodes the `TiDBCloud Integration dashboard` url.
func GetDatadogDashboardUrl(site central.DatadogSite) string {
	var (
		authority     string
		integrationID int
	)
	switch site {
	case central.DatadogSite_DATADOG_SITE_US1:
		authority = "app.datadoghq.com"
		integrationID = 30586
	case central.DatadogSite_DATADOG_SITE_US3:
		authority = "us3.datadoghq.com"
		integrationID = 431
	case central.DatadogSite_DATADOG_SITE_US5:
		authority = "us5.datadoghq.com"
		integrationID = 421
	case central.DatadogSite_DATADOG_SITE_EU1:
		authority = "app.datadoghq.eu"
		integrationID = 447
	case central.DatadogSite_DATADOG_SITE_AP1:
		authority = "ap1.datadoghq.com" // TODO
		integrationID = 565
	default:
		authority = "app.datadoghq.com"
		integrationID = 30586
	}
	return fmt.Sprintf("https://%s/dash/integration/%d/tidbcloud-cluster-overview", authority, integrationID)
}

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