package integration

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"log"
	"strings"

	"github.com/google/uuid"
	nlog "github.com/ngaut/log"
	"github.com/pingcap/errcode"
	consolev1 "github.com/tidbcloud/metrics-integration/global/api/integration/v1"
	"github.com/tidbcloud/metrics-integration/global/internal/domain/consts"
	"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/config"
	"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/internal/infrastructure/promconfig"
	"github.com/tidbcloud/metrics-integration/global/pkg/helm/pipeline"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"
)

func getKey() string {
	return strings.ReplaceAll(uuid.New().String(), "-", "")
}

func encode(input string) string {
	keyHashBytes := sha256.Sum256([]byte(input))
	return hex.EncodeToString(keyHashBytes[:])
}

// CreateNewrelicIntegration enables the newrelic integration for a given customer project.
// @param payload - types of data that Newrelic will collect.
func CreateMetricsKey(ctx context.Context, clusterId entity.ID, creator string) (metricsKey *entity.MetricsKey, queryEndpoint string, err error) {
	// auditlog
	defer func() {
		details := auditlog.Details{
			auditlog.KeyDescription:     "a metrics integration is created",
			auditlog.KeyIntegrationType: "prometheus",
			auditlog.KeyError:           err,
		}
		user := account.GetUserFromContext(ctx)
		eventType := auditlog.EventCreateMetricsIntegration
		eventSubmitErr := auditlog.NewEvent(ctx, eventType).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(user.TenantID).Submit()
		if eventSubmitErr != nil {
			nlog.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 nil, "", fmt.Errorf("failed to get cluster info: %w", err)
	}

	orgId := entity.ID(cluster.OrganizationId)
	projectId := entity.ID(cluster.ProjectId)
	region := cluster.Region
	cloudProvider := cluster.CloudProvider
	cloudProviderType, err := normalizeIntegrationCloudProviderType(cloudProvider)
	if err != nil {
		return nil, "", err
	}
	queryEndpoint = ""
	if endpoint := config.Global().RegionalExecutorConfig.GetPrometheusQueryEndpoint(cloudProvider, region); endpoint == "" {
		return nil, "", fmt.Errorf("no prometheus query endpoint for provider %s region %s", cloudProvider, region)
	} else {
		queryEndpoint = endpoint
	}
	// insert metrics key into metadb
	key := getKey()
	encoded := encode(key)
	//update config map

	// return the metrics
	metricsKey = &entity.MetricsKey{
		OrgID:             orgId,
		ProjectID:         projectId,
		ClusterID:         clusterId,
		CloudProviderType: cloudProviderType,
		KeyHash:           encoded,
		Region:            region,
	}

	// Ensure a GeneralIntegration exists for this cluster+provider, and use its ID
	if err := di.DiCall(func(db *gorm.DB) error {
		err = db.Transaction(func(tx *gorm.DB) error {
			var existingKeys []*entity.MetricsKey
			if findErr := tx.Where("cluster_id = ?", clusterId).Find(&existingKeys).Error; findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
				return findErr
			}
			if len(existingKeys) >= consts.MaxMetricsKeyCount {
				return fmt.Errorf("exceed max metrics key count %d", consts.MaxMetricsKeyCount)
			}
			return nil
		})
		if err != nil {
			return err
		}

		return nil
	}); err != nil {
		return nil, "", fmt.Errorf("failed to validate the limit of number of metrics key: %v", err)
	}

	yamlBytes, err := pipeline.BuildTargetClusterConfigYAML(uint64(clusterId))
	if err != nil {
		return nil, "", err
	}

	err = promconfig.RemoteAddMetricsKey(ctx, fmt.Sprintf("%d", clusterId), encoded, string(yamlBytes), config.Global().RegionalExecutorConfig.GetServerAddr(cloudProvider, region))
	if err != nil {
		return nil, "", fmt.Errorf("failed to remote add metrics key to cluster: %v", err)
	}

	if err := di.DiCall(func(db *gorm.DB) error {
		err = db.Transaction(func(tx *gorm.DB) error {
			genPtr := &entity.GeneralIntegration{
				TenantID:     orgId,
				ProjectID:    projectId,
				ClusterID:    clusterId,
				ProviderType: entity.IntegrationTypePrometheus,
				Creator:      creator,
			}
			if err := tx.Create(genPtr).Error; err != nil {
				return fmt.Errorf("create general integration failed: %w", err)
			}
			metricsKey.ID = genPtr.ID

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

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

		return nil
	}); err != nil {
		fmt.Println("insert metrics key failed: ", err)
		return nil, "", fmt.Errorf("failed to insert metrics key to db")
	}

	return metricsKey, queryEndpoint, nil
}
func DeleteMetricsKey(ctx context.Context, metricsKeyId uint64) (err error) {
	// audit log
	defer func() {
		user := account.GetUserFromContext(ctx)
		details := auditlog.Details{
			auditlog.KeyDescription:     "a metrics integration is deleted",
			auditlog.KeyIntegrationType: "prometheus",
			auditlog.KeyError:           err,
		}
		eventSubmitErr := auditlog.NewEvent(ctx, auditlog.EventDeleteMetricsIntegration).SetResult(err == nil).SetDetails(details.Build()).SetOrgID(user.TenantID).Submit()
		if eventSubmitErr != nil {
			nlog.Error(ctx, "DeleteMetricsKey submit event fail",
				zap.Uint64("tenantID", user.TenantID),
				zap.Uint64("userID", user.ID),
				zap.Error(eventSubmitErr))
		}
	}()

	// check the existence
	var metricsKey *entity.MetricsKey
	if err := di.DiCall(func(db *gorm.DB) error {
		if err := db.Transaction(func(tx *gorm.DB) error {
			if findErr := tx.Where("id = ?", metricsKeyId).First(&metricsKey).Error; findErr != nil {
				return findErr
			}

			return nil
		}); err != nil {
			return err
		}
		return nil
	}); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			log.Printf("metrics key not found, cluster id is %d, metrics key id is %d", metricsKey.ClusterID, metricsKeyId)
			return nil
		}
		return fmt.Errorf("failed to delete metrics key: %v", err)
	}

	// remote delete
	err = promconfig.RemoteDeleteMetricsKey(ctx, fmt.Sprintf("%d", metricsKey.ClusterID), metricsKey.KeyHash, config.Global().RegionalExecutorConfig.GetServerAddr(string(metricsKey.CloudProviderType), metricsKey.Region))
	if err != nil {
		return fmt.Errorf("failed to remote delete metrics key to cluster: %v", err)
	}

	// delete from db
	if err := di.DiCall(func(db *gorm.DB) error {
		err = db.Transaction(func(tx *gorm.DB) error {
			if err := tx.Where("cluster_id = ?", metricsKey.ClusterID).Where("key_hash = ?", metricsKey.KeyHash).Delete(&entity.MetricsKey{}).Error; err != nil {
				return err
			}
			// also attempt to delete corresponding GeneralIntegration with same id
			// (skip if it doesn't exist)
			if err := tx.Where("id = ?", metricsKey.ID).Delete(&entity.GeneralIntegration{}).Error; err != nil {
				// If the error is record not found, ignore; otherwise return
				return err
			}
			return nil
		})
		if err != nil {
			return err
		}
		return nil
	}); err != nil {
		return fmt.Errorf("failed delete metrics key from db: %v", err)
	}

	return nil
}

func GetAllMetricsKey(ctx context.Context, clusterId uint64) (resp *consolev1.GetAllMetricsKeyResp, err error) {
	clusterID := entity.ID(clusterId)

	metricsKeys := make([]*entity.MetricsKey, 0)

	if err := di.DiCall(func(db *gorm.DB) error {
		return db.Where("cluster_id = ?", clusterID).Find(&metricsKeys).Error
	}); err != nil {
		return nil, errcode.NewInternalErr(fmt.Errorf("failed to get metrics keys: %w", err))
	}

	resp = &consolev1.GetAllMetricsKeyResp{}

	queryEndpoint := ""
	for _, metricsKey := range metricsKeys {
		if queryEndpoint == "" {
			queryEndpoint = config.Global().RegionalExecutorConfig.GetPrometheusQueryEndpoint(string(metricsKey.CloudProviderType), metricsKey.Region)
		}
		resp.MetricsKeys = append(resp.MetricsKeys, &consolev1.MetricsKey{
			KeyId:     uint64(metricsKey.ID),
			Key:       metricsKey.KeyHash,
			CreatedAt: timestamppb.New(metricsKey.CreatedAt),
		})
	}
	resp.QueryEndpoint = queryEndpoint

	return resp, nil
}
