package integration

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"net/http"

	"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"
)

// CreateOrEnableNewrelicIntegration enables the newrelic integration for a given customer project.
// @param payload - types of data that Newrelic will collect.
func (m Model) CreateOrEnableNewrelicIntegration(ctx context.Context, projectId entity.ID, payloads []central.IntegrationPayload, site central.NewrelicSite, 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 := NewrelicSiteRestToDB(site)
	pid := projectId
	maskedKey = MaskNewrelicAPIKey(apiKey)
	newrelicIntegration := entity.Integration{
		ProjectID:            pid,
		ProviderType:         entity.IntegrationTypeNewrelic,
		Status:               entity.IntegrationStatusActive,
		StatusReason:         "",
		Payloads:             entity.IntegrationPayloads{Payloads: payloadsDB},
		NewrelicSite:         siteDB,
		MaskedNewrelicApiKey: maskedKey,
	}

	// Encrypt the api key with AWS KMS (with the customized master key).
	encryptedKey, _ := encrypt.EncryptBase64(m.encryptor, encrypt.Secret(apiKey))
	newrelicIntegration.EncryptedNewrelicApiKey = 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.IntegrationTypeNewrelic).One(i); err != nil {
			if !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}

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

			return nil
		}

		newrelicIntegration.ID = i.ID
		err := newrelicIntegration.Update(
			tx,
			entity.IntegrationDBSchema.Status,
			entity.IntegrationDBSchema.StatusReason,
			entity.IntegrationDBSchema.Payloads,
			entity.IntegrationDBSchema.NewrelicSite,
			entity.IntegrationDBSchema.MaskedNewrelicApiKey,
			entity.IntegrationDBSchema.EncryptedNewrelicApiKey,
		)
		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
}

// DisableNewrelicIntegration disable the newrelic integration for a given customer project.
func (m Model) DisableNewrelicIntegration(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.IntegrationTypeNewrelic).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
}

// ValidateNewrelicAPIKey validates the API key through Newrelic site.
func (m Model) ValidateNewrelicAPIKey(_ context.Context, apiKey string, site central.NewrelicSite) (valid bool, err error) {
	req, err := http.NewRequest(http.MethodPost, GetNewrelicSiteLogEp(site), nil)
	if err != nil {
		return false, err
	}

	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Api-Key", apiKey)

	resp, err := m.httpc.Do(req)

	if err != nil {
		return false, err
	}

	if resp.StatusCode != http.StatusAccepted {
		switch resp.StatusCode {
		case http.StatusForbidden:
			return false, fmt.Errorf("please check the key or region")
		case http.StatusInternalServerError:
			return false, fmt.Errorf("newRelic's API service is temporarily unavailable, please try again later")
		default:
			return false, fmt.Errorf(resp.Status)
		}
	}

	return true, nil
}

func GetNewrelicSiteEndpoint(site central.NewrelicSite) string {
	// DatadogSiteUrl refers to [https://docs.newrelic.com/docs/more-integrations/open-source-telemetry-integrations/opentelemetry/get-started/opentelemetry-set-up-your-app/#direct-export]
	switch site {
	case central.NewrelicSite_NEWRELIC_SITE_EU_OTLP:
		return "https://otlp.eu01.nr-data.net:443"
	case central.NewrelicSite_NEWRELIC_SITE_US_OTLP:
		return "https://otlp.nr-data.net:443"
	default:
		return "https://otlp.nr-data.net:443"
	}
}

// GetNewrelicSiteDashboard returns the endpoint of the Newrelic dashboard·
func GetNewrelicSiteDashboard(site central.NewrelicSite) string {
	return "https://one.newrelic.com/dashboards/"
}

func GetNewrelicSiteLogEp(site central.NewrelicSite) string {
	// DatadogSiteUrl refers to [https://docs.newrelic.com/docs/more-integrations/open-source-telemetry-integrations/opentelemetry/get-started/opentelemetry-set-up-your-app/#direct-export]
	switch site {
	case central.NewrelicSite_NEWRELIC_SITE_EU_OTLP:
		return "https://log-api.eu.newrelic.com/log/v1"
	case central.NewrelicSite_NEWRELIC_SITE_US_OTLP:
		return "https://log-api.newrelic.com/log/v1"
	default:
		return "https://log-api.newrelic.com/log/v1"
	}
}

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