package integration

import (
	"context"
	"fmt"
	"time"

	"github.com/juju/errors"
	"github.com/pingcap/errcode"
	"github.com/shiningrush/fastflow/pkg/mod"
	"github.com/tidbcloud/metrics-integration/global/internal/domain/entity"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/config"
	"github.com/tidbcloud/metrics-integration/global/internal/infrastructure/di"
	"github.com/tidbcloud/metrics-integration/global/pkg/cvt"
	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"

	opapiv1beta1 "github.com/tidbcloud/metrics-integration/global/api/integration/openapi/v1beta1"
)

// ListIntegrationsByCluster returns all integrations for a cluster with provider-specific details attached.
// It first queries general integrations, then batch-fetches provider details and attaches them.
func ListIntegrationsByClusterForOpAPI(ctx context.Context, clusterId uint64) (resp *opapiv1beta1.ListIntegrationsResp, err error) {
	generals, err := getGeneralsByCluster(clusterId)
	if err != nil {
		return nil, err
	}

	// collect ids by provider
	datadogIDs := make([]uint64, 0)
	newrelicIDs := make([]uint64, 0)
	prometheusIDs := make([]uint64, 0)
	for _, g := range generals {
		switch g.ProviderType {
		case entity.IntegrationTypeDatadog:
			datadogIDs = append(datadogIDs, uint64(g.ID))
		case entity.IntegrationTypeNewrelic:
			newrelicIDs = append(newrelicIDs, uint64(g.ID))
		case entity.IntegrationTypePrometheus:
			prometheusIDs = append(prometheusIDs, uint64(g.ID))
		}
	}

	// batch fetch provider details
	integrationsMap, err := getDatadogNewrelicIntegrationsByIDs(append(datadogIDs, newrelicIDs...))
	if err != nil {
		return nil, err
	}

	metricsMap, err := getMetricsKeysByIDs(prometheusIDs)
	if err != nil {
		return nil, err
	}

	// assemble response
	items := make([]*opapiv1beta1.IntegrationStatusResp, 0, len(generals))
	for _, g := range generals {
		items = append(items, assembleIntegrationResp(g, integrationsMap, metricsMap))
	}

	resp = &opapiv1beta1.ListIntegrationsResp{
		Items: items,
		Total: int32(len(items)),
	}
	return resp, nil
}

// getGeneralsByCluster returns general integrations for a cluster.
func getGeneralsByCluster(clusterID uint64) ([]entity.GeneralIntegration, error) {
	var generals []entity.GeneralIntegration
	if err := di.DiCall(func(db *gorm.DB) error {
		return db.Where("cluster_id = ?", clusterID).Find(&generals).Error
	}); err != nil {
		return nil, errcode.NewInternalErr(errors.Annotate(err, "list general integrations failed"))
	}
	return generals, nil
}

// getDatadogNewrelicIntegrationsByIDs returns a map of integration ID -> Integration for the given IDs.
func getDatadogNewrelicIntegrationsByIDs(ids []uint64) (map[uint64]entity.Integration, error) {
	out := make(map[uint64]entity.Integration)
	if len(ids) == 0 {
		return out, nil
	}
	// convert []uint64 to interface{} slice accepted by gorm IN
	var ints []entity.Integration
	if err := di.DiCall(func(db *gorm.DB) error {
		return db.Where("id IN ?", ids).Find(&ints).Error
	}); err != nil {
		return nil, errcode.NewInternalErr(errors.Annotate(err, "list integrations details failed"))
	}
	for _, it := range ints {
		out[uint64(it.ID)] = it
	}
	return out, nil
}

// getMetricsKeysByIDs returns a map of metrics key ID -> MetricsKey for the given IDs.
func getMetricsKeysByIDs(ids []uint64) (map[uint64]entity.MetricsKey, error) {
	out := make(map[uint64]entity.MetricsKey)
	if len(ids) == 0 {
		return out, nil
	}
	var mks []entity.MetricsKey
	if err := di.DiCall(func(db *gorm.DB) error {
		return db.Where("id IN ?", ids).Find(&mks).Error
	}); err != nil {
		return nil, errcode.NewInternalErr(errors.Annotate(err, "list metrics keys failed"))
	}
	for _, m := range mks {
		out[uint64(m.ID)] = m
	}
	return out, nil
}

// assembleIntegrationResp creates an IntegrationStatusResp from the general record and detail maps.
func assembleIntegrationResp(g entity.GeneralIntegration, integrationsMap map[uint64]entity.Integration, metricsMap map[uint64]entity.MetricsKey) *opapiv1beta1.IntegrationStatusResp {
	common := &opapiv1beta1.IntegrationStatusResp{
		Id:         uint64(g.ID),
		Type:       cvt.TypeDBToOpenAPI(g.ProviderType),
		CreateTime: timestamppb.New(g.CreatedAt),
		UpdateTime: timestamppb.New(func() time.Time {
			if g.UpdatedAt != nil {
				return *g.UpdatedAt
			}
			return g.CreatedAt
		}()),
		Creator: g.Creator,
	}

	switch g.ProviderType {
	case entity.IntegrationTypeDatadog:
		if it, ok := integrationsMap[uint64(g.ID)]; ok {
			common.Spec = &opapiv1beta1.IntegrationStatusResp_Datadog{Datadog: &opapiv1beta1.DatadogIntegrationStatusResp{
				State:        cvt.StatusDBToOpenAPIState(it.Status),
				StatusReason: it.StatusReason,
				Site:         cvt.DatadogSiteDBToOpenAPI(it.DatadogSite),
				MaskedApiKey: it.MaskedDatadogApiKey,
			}}
		}
	case entity.IntegrationTypeNewrelic:
		if it, ok := integrationsMap[uint64(g.ID)]; ok {
			common.Spec = &opapiv1beta1.IntegrationStatusResp_Newrelic{Newrelic: &opapiv1beta1.NewrelicIntegrationStatusResp{
				State:        cvt.StatusDBToOpenAPIState(it.Status),
				StatusReason: it.StatusReason,
				Site:         cvt.NewrelicSiteDBToOpenAPI(it.NewrelicSite),
				MaskedApiKey: it.MaskedNewrelicApiKey,
			}}
		}
	case entity.IntegrationTypePrometheus:
		if mk, ok := metricsMap[uint64(g.ID)]; ok {
			common.Spec = &opapiv1beta1.IntegrationStatusResp_Prometheus{Prometheus: &opapiv1beta1.CreateMetricsKeyResp{
				Key:           mk.KeyHash,
				QueryEndpoint: config.Global().RegionalExecutorConfig.GetPrometheusQueryEndpoint(string(mk.CloudProviderType), mk.Region),
			}}
		}
	}

	return common
}

// DeleteDatadogNewrelicIntegration
func DeleteDatadogNewrelicIntegration(ctx context.Context, id entity.ID) error {
	if err := di.DiCall(func(db *gorm.DB) error {
		// update status to none to make the frontend easy; update by the provided id regardless of existence
		if err := db.Model(&entity.Integration{}).Where("id = ?", id).Update("status", entity.IntegrationStatusNone).Error; err != nil {
			return fmt.Errorf("failed to update status: %w", err)
		}

		// Always attempt to run the delete pipeline using the provided id
		if _, err := mod.GetCommander().RunDagWithTags("delete-pipeline", map[string]string{
			"id": fmt.Sprintf("%d", id),
		}, map[string]string{}); err != nil {
			return err
		}

		return nil
	}); err != nil {
		return fmt.Errorf("disable integration failed: %v", err)
	}

	return nil
}
