package pipeline

import (
	"fmt"
	"log"

	mapstructure "github.com/go-viper/mapstructure/v2"
	"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/helm"
	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chart/loader"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"sigs.k8s.io/yaml"
)

type PipelineConfig struct {
	PipelineID               string                 `json:"pipelineID,omitempty"`
	TargetCluster            TargetCluster          `json:"clusterInfo,omitempty"`
	PipelineMetaData         *entity.Integration    `json:"pipelineMetaData,omitempty"`
	DecryptedAPIKey          string                 `json:"decryptedAPIKey,omitempty"`
	ChartPath                string                 `json:"chartPath,omitempty"`
	PipelineImageRepo        string                 `json:"pipelineImageRepo,omitempty"`
	PipelineImageTag         string                 `json:"pipelineImageTag,omitempty"`
	VectorImageRepo          string                 `json:"vectorImageRepo,omitempty"`
	VectorImageTag           string                 `json:"vectorImageTag,omitempty"`
	O11yGlobalServerConfig   map[string]interface{} `json:"o11yGlobalServerConfig,omitempty"`
	EnableBackfillBatchMode  bool                   `json:"enableBackfillBatchMode,omitempty"`
	DisableDedicatedNodepool bool                   `json:"disableDedicatedNodepool,omitempty"`
	// ExcludeImageUpdate controls whether to exclude image configuration from helm values
	// Set to true for target cluster only updates to avoid image upgrades
	ExcludeImageUpdate bool `json:"excludeImageUpdate,omitempty"`
}

var _ helm.DryRunablePackage = &PipelineConfig{}

func NewPipelineConfig(pipelineID string, targetCluster TargetCluster, pipelineMetaData *entity.Integration, decryptedAPIKey string, excludeImageUpdate bool) *PipelineConfig {
	// Get config values once and store them in the struct
	pipelineChart := config.Global().HelmChartConfigs.GetChart(config.PipelineChartName)
	vectorChart := config.Global().HelmChartConfigs.GetChart(config.VectorChartName)

	// For multicloud regions, prefer provider-qualified image overrides and fall back to legacy region-only keys.
	if imageRepo := config.Global().RegionalExecutorConfig.GetPipelineImageOverride(targetCluster.CloudProviderName, targetCluster.RegionName); imageRepo != "" {
		pipelineChart.ImageRepo = imageRepo
	}

	// Process the o11y global server config
	var o11yGlobalServerConfig map[string]interface{}
	if err := mapstructure.Decode(config.Global().O11yGlobalServerConfig, &o11yGlobalServerConfig); err != nil {
		log.Fatalf("failed to decode o11y global server config: %v", err)
	}

	return &PipelineConfig{
		PipelineID:               pipelineID,
		PipelineMetaData:         pipelineMetaData,
		DecryptedAPIKey:          decryptedAPIKey,
		ChartPath:                pipelineChart.ChartPath,
		PipelineImageRepo:        pipelineChart.ImageRepo,
		PipelineImageTag:         pipelineChart.ImageTag,
		VectorImageRepo:          vectorChart.ImageRepo,
		VectorImageTag:           vectorChart.ImageTag,
		O11yGlobalServerConfig:   o11yGlobalServerConfig,
		EnableBackfillBatchMode:  config.Global().PipelineDefaults.EnableBackfillBatchMode,
		DisableDedicatedNodepool: config.Global().DebugConfig.DisableDedicatedNodepool,
		TargetCluster:            targetCluster,
		ExcludeImageUpdate:       excludeImageUpdate,
	}
}

func (c *PipelineConfig) GetNamespace() string {
	return fmt.Sprintf("pipeline-%s", c.PipelineID)
}

func (c *PipelineConfig) GetName() string {
	return fmt.Sprintf("pipeline-%s", c.PipelineID)
}

func (c *PipelineConfig) DryRun() bool {
	return false
}

func (c *PipelineConfig) ToHelmChart() (result *chart.Chart, err error) {
	result, err = loader.Load(c.ChartPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load helm chart %q: %w", c.ChartPath, err)
	}
	return
}

func (c *PipelineConfig) ToHelmValues() map[string]interface{} {
	serverConfig := BuildServerConfig(c.TargetCluster, c.O11yGlobalServerConfig)

	// Process the target cluster config
	// process with downstream target
	var target map[string]interface{}
	var downstreamType string
	switch c.PipelineMetaData.ProviderType {
	case entity.IntegrationTypeDatadog:
		downstreamType = "datadog"
		target = map[string]interface{}{
			"type":            "datadog_metrics",
			"inputs":          []string{"datadog_transformation"},
			"default_api_key": c.DecryptedAPIKey,
			"site":            c.PipelineMetaData.DatadogSite.GetDatadogSiteUrl(),
		}
	case entity.IntegrationTypeNewrelic:
		downstreamType = "newrelic"

		var newRelicSite string
		switch c.PipelineMetaData.NewrelicSite {
		case entity.NewrelicSiteUSOTLP:
			newRelicSite = "us"
		case entity.NewrelicSiteEUOTLP:
			newRelicSite = "eu"
		}
		target = map[string]interface{}{
			"type":        "new_relic",
			"inputs":      []string{"newrelic_transformation"},
			"account_id":  "account_id_placeholder",
			"api":         "metrics",
			"license_key": c.DecryptedAPIKey,
			"region":      newRelicSite,
		}
	}

	values := map[string]interface{}{
		"service": map[string]interface{}{
			"type": "ClusterIP",
			"port": 8080,
		},
		"vector": map[string]interface{}{
			"enable":                   true,
			"downstream_type":          downstreamType,
			"deployment":               map[string]interface{}{},
			"disableDedicatedNodepool": c.DisableDedicatedNodepool,
			"sink":                     target,
		},
		"pipeline": map[string]interface{}{
			"targetClusterId": c.TargetCluster.Id,
			"serverConfig":    serverConfig,
			"backfill": map[string]interface{}{
				"batchModeEnabled": c.EnableBackfillBatchMode,
			},
		},
		"disableDedicatedNodepool": c.DisableDedicatedNodepool,
	}

	// Only include image configuration if not excluded
	// When ExcludeImageUpdate is true, we skip setting image values to use defaults from values.yaml
	// TODO: fix exclude image update
	// if !c.ExcludeImageUpdate {

	values["image"] = map[string]interface{}{
		"repository": c.PipelineImageRepo,
		"pullPolicy": "IfNotPresent",
		"tag":        c.PipelineImageTag,
	}
	values["vector"].(map[string]interface{})["deployment"].(map[string]interface{})["image"] = map[string]interface{}{
		"repository": c.VectorImageRepo,
		"tag":        c.VectorImageTag,
	}
	// }

	return values
}

func (c *PipelineConfig) GetCRDs() ([]*apiextensionsv1.CustomResourceDefinition, error) {
	return nil, nil
}

func BuildServerConfig(targetCluster TargetCluster, o11yGlobalServerConfig map[string]interface{}) map[string]interface{} {
	targetClusterConfig := map[string]interface{}{
		"id":                targetCluster.Id,
		"name":              targetCluster.Name,
		"cloudProviderName": targetCluster.CloudProviderName,
		"regionName":        targetCluster.RegionName,
		"tenantId":          targetCluster.TenantId,
		"projectId":         targetCluster.ProjectId,
		"projectName":       targetCluster.ProjectName,
		"servicePlan":       targetCluster.ServicePlan,
	}
	componentConfigs := make([]map[string]interface{}, 0, len(targetCluster.Components))
	for _, component := range targetCluster.Components {
		componentConfigs = append(componentConfigs, map[string]interface{}{
			"componentType": component.ComponentType,
			"cpuNum":        component.CpuNum,
		})
	}
	targetClusterConfig["components"] = componentConfigs

	clonedO11yGlobalServerConfig := make(map[string]interface{}, len(o11yGlobalServerConfig))
	for key, value := range o11yGlobalServerConfig {
		clonedO11yGlobalServerConfig[key] = value
	}

	return map[string]interface{}{
		"server": map[string]interface{}{
			"logLevel":          "info",
			"grpcPort":          10000,
			"metricsPort":       9090,
			"metricsListenAddr": "0.0.0.0:8080",
		},
		"o11yGlobalServer": clonedO11yGlobalServerConfig,
		"targetClusters": []map[string]interface{}{
			targetClusterConfig,
		},
	}
}

func BuildServerConfigYAML(targetCluster TargetCluster, o11yGlobalServerConfig map[string]interface{}) (string, error) {
	data, err := yaml.Marshal(BuildServerConfig(targetCluster, o11yGlobalServerConfig))
	if err != nil {
		return "", fmt.Errorf("failed to marshal pipeline server config: %w", err)
	}
	return string(data), nil
}
