package builder

import (
	"context"
	"encoding/json"
	"fmt"

	"gopkg.in/yaml.v3"

	"vanderlande.com/ittp/appstack/rig-operator/api/v1alpha1"
	"vanderlande.com/ittp/appstack/rig-operator/internal/provider"
)

// ChartConfig holds the helm settings extracted from the YAML _defaults
// The Controller needs this to know WHICH chart to fetch.
type ChartConfig struct {
	Repo    string
	Name    string
	Version string
}

type MasterBuilder struct {
	strategy     provider.Strategy
	baseTemplate []byte
	chartConfig  ChartConfig
}

func NewMasterBuilder(strategy provider.Strategy, baseTemplate []byte) *MasterBuilder {
	b := &MasterBuilder{
		strategy:     strategy,
		baseTemplate: baseTemplate,
		// Safe defaults
		chartConfig: ChartConfig{
			Name: "oci://ghcr.io/rancherfederal/charts/rancher-cluster-templates",
		},
	}
	return b
}

// GetChartConfig returns the chart details found in the template.
func (b *MasterBuilder) GetChartConfig() ChartConfig {
	return b.chartConfig
}

// Build orchestrates the values generation process
func (b *MasterBuilder) Build(ctx context.Context, cbp *v1alpha1.ClusterBlueprint, credentialSecret string) (map[string]interface{}, error) {
	values := make(map[string]interface{})
	if err := yaml.Unmarshal(b.baseTemplate, &values); err != nil {
		return nil, fmt.Errorf("failed to unmarshal base template: %w", err)
	}

	// 1. Extract Chart Config from _defaults (Legacy Logic Ported)
	// We do this so the Controller knows what version to install.
	if defaults, ok := values["_defaults"].(map[string]interface{}); ok {
		if chartCfg, ok := defaults["helmChart"].(map[string]interface{}); ok {
			if v, ok := chartCfg["repo"].(string); ok {
				b.chartConfig.Repo = v
			}
			if v, ok := chartCfg["name"].(string); ok {
				b.chartConfig.Name = v
			}
			if v, ok := chartCfg["version"].(string); ok {
				b.chartConfig.Version = v
			}
		}
	}

	// 2. Generate Node Pools (Delegated to Strategy)
	// [DIFFERENCE]: We don't loop here. The Strategy knows how to map CBP -> Provider NodePools.
	nodePools, err := b.strategy.GenerateNodePools(ctx, cbp)
	if err != nil {
		return nil, fmt.Errorf("strategy failed to generate node pools: %w", err)
	}

	// 3. Get Global Overrides (Delegated to Strategy)
	// [DIFFERENCE]: We don't hardcode "cloud_provider_name" here. The Strategy returns it.
	overrides, err := b.strategy.GetGlobalOverrides(ctx, cbp, credentialSecret)
	if err != nil {
		return nil, fmt.Errorf("strategy failed to get global overrides: %w", err)
	}

	// 4. Inject Logic into the Helm Structure
	if clusterMap, ok := values["cluster"].(map[string]interface{}); ok {
		clusterMap["name"] = cbp.Name

		if configMap, ok := clusterMap["config"].(map[string]interface{}); ok {
			configMap["kubernetesVersion"] = cbp.Spec.KubernetesVersion

			// Ensure globalConfig exists
			if _, ok := configMap["globalConfig"]; !ok {
				configMap["globalConfig"] = make(map[string]interface{})
			}
			globalConfig := configMap["globalConfig"].(map[string]interface{})

			// Inject Overrides
			for k, v := range overrides {
				// A. Handle specific Global Config keys
				if k == "cloud_provider_name" || k == "cloud_provider_config" {
					globalConfig[k] = v
					continue
				}

				// B. Handle Chart Values (CCM/CSI Addons)
				if k == "chartValues" {
					if existingChartVals, ok := configMap["chartValues"].(map[string]interface{}); ok {
						if newChartVals, ok := v.(map[string]interface{}); ok {
							for ck, cv := range newChartVals {
								existingChartVals[ck] = cv
							}
						}
					} else {
						configMap["chartValues"] = v
					}
					continue
				}

				// C. Default: Inject at Root level
				values[k] = v
			}
		}
	}

	// 5. Inject Node Pools
	// We marshal/unmarshal to ensure JSON tags from the Strategy structs are respected
	tempJSON, _ := json.Marshal(nodePools)
	var cleanNodePools interface{}
	_ = json.Unmarshal(tempJSON, &cleanNodePools)
	values["nodepools"] = cleanNodePools

	// 6. Cleanup internal keys
	delete(values, "_defaults")

	return values, nil
}
