package maximizer

import "sort"

const maxVariants = 5

// OptimizedVariant is a single compliant configuration with scoring.
type OptimizedVariant struct {
	Config         DimensionConfig  `json:"config"`
	Evaluation     *EvaluationResult `json:"evaluation"`
	Deltas         []DimensionDelta `json:"deltas"`
	DeltaCount     int              `json:"delta_count"`
	SafetyScore    int              `json:"safety_score"`
	UtilityScore   int              `json:"utility_score"`
	CompositeScore float64          `json:"composite_score"`
	Rationale      string           `json:"rationale"`
}

// OptimizationResult contains the original evaluation and ranked compliant variants.
type OptimizationResult struct {
	OriginalConfig    DimensionConfig    `json:"original_config"`
	OriginalCompliant bool               `json:"original_compliant"`
	OriginalEval      *EvaluationResult  `json:"original_evaluation"`
	Variants          []OptimizedVariant `json:"variants"`
	MaxSafeConfig     *OptimizedVariant  `json:"max_safe_config"`
}

// Optimizer finds the maximum compliant configuration variant.
type Optimizer struct {
	evaluator *Evaluator
	weights   ScoreWeights
}

// NewOptimizer creates an optimizer backed by the given evaluator.
func NewOptimizer(evaluator *Evaluator) *Optimizer {
	return &Optimizer{evaluator: evaluator, weights: DefaultWeights}
}

// Optimize takes a desired (possibly non-compliant) config and returns
// ranked compliant alternatives.
func (o *Optimizer) Optimize(desired *DimensionConfig) *OptimizationResult {
	eval := o.evaluator.Evaluate(desired)
	result := &OptimizationResult{
		OriginalConfig:    *desired,
		OriginalCompliant: eval.IsCompliant,
		OriginalEval:      eval,
	}

	if eval.IsCompliant {
		variant := o.scoreVariant(desired, desired, eval)
		variant.Rationale = "Konfiguration ist bereits konform"
		result.Variants = []OptimizedVariant{variant}
		result.MaxSafeConfig = &result.Variants[0]
		return result
	}

	// Check for hard prohibitions that cannot be optimized
	if o.hasProhibitedClassification(desired) {
		result.Variants = []OptimizedVariant{}
		return result
	}

	candidates := o.generateCandidates(desired, eval)
	result.Variants = candidates
	if len(candidates) > 0 {
		result.MaxSafeConfig = &result.Variants[0]
	}
	return result
}

func (o *Optimizer) hasProhibitedClassification(config *DimensionConfig) bool {
	return config.RiskClassification == RiskProhibited
}

// generateCandidates builds compliant variants by fixing violations.
func (o *Optimizer) generateCandidates(desired *DimensionConfig, eval *EvaluationResult) []OptimizedVariant {
	// Strategy 1: Fix all violations in one pass (greedy nearest fix)
	greedy := o.greedyFix(desired, eval)
	var candidates []OptimizedVariant

	if greedy != nil {
		greedyEval := o.evaluator.Evaluate(&greedy.Config)
		if greedyEval.IsCompliant {
			v := o.scoreVariant(desired, &greedy.Config, greedyEval)
			v.Rationale = "Minimale Anpassung — naechster konformer Zustand"
			candidates = append(candidates, v)
		}
	}

	// Strategy 2: Conservative variant (maximum safety)
	conservative := o.conservativeFix(desired, eval)
	if conservative != nil {
		consEval := o.evaluator.Evaluate(&conservative.Config)
		if consEval.IsCompliant {
			v := o.scoreVariant(desired, &conservative.Config, consEval)
			v.Rationale = "Konservative Variante — maximale regulatorische Sicherheit"
			candidates = append(candidates, v)
		}
	}

	// Strategy 3: Fix restricted dimensions too (belt-and-suspenders)
	enhanced := o.enhancedFix(desired, eval)
	if enhanced != nil {
		enhEval := o.evaluator.Evaluate(&enhanced.Config)
		if enhEval.IsCompliant {
			v := o.scoreVariant(desired, &enhanced.Config, enhEval)
			v.Rationale = "Erweiterte Variante — alle Einschraenkungen vorab behoben"
			candidates = append(candidates, v)
		}
	}

	// Deduplicate and sort by composite score
	candidates = deduplicateVariants(candidates)
	sort.Slice(candidates, func(i, j int) bool {
		return candidates[i].CompositeScore > candidates[j].CompositeScore
	})
	if len(candidates) > maxVariants {
		candidates = candidates[:maxVariants]
	}
	return candidates
}

// greedyFix applies the minimum change per violated dimension.
func (o *Optimizer) greedyFix(desired *DimensionConfig, eval *EvaluationResult) *OptimizedVariant {
	fixed := desired.Clone()

	// Fix FORBIDDEN zones
	for dim, zi := range eval.ZoneMap {
		if zi.Zone != ZoneForbidden {
			continue
		}
		o.fixDimension(&fixed, dim, eval)
	}

	// Fix RESTRICTED zones (required values not met)
	for _, restriction := range eval.Restrictions {
		for dim, requiredVal := range restriction.Required {
			fixed.SetValue(dim, requiredVal)
		}
	}

	// Re-evaluate and iterate (max 3 passes to converge)
	for i := 0; i < 3; i++ {
		reEval := o.evaluator.Evaluate(&fixed)
		if reEval.IsCompliant {
			break
		}
		for dim, zi := range reEval.ZoneMap {
			if zi.Zone == ZoneForbidden {
				o.fixDimension(&fixed, dim, reEval)
			}
		}
		for _, restriction := range reEval.Restrictions {
			for dim, requiredVal := range restriction.Required {
				fixed.SetValue(dim, requiredVal)
			}
		}
	}

	return &OptimizedVariant{Config: fixed}
}

// conservativeFix chooses the safest allowed value for each violated dimension.
func (o *Optimizer) conservativeFix(desired *DimensionConfig, eval *EvaluationResult) *OptimizedVariant {
	fixed := desired.Clone()

	for dim, zi := range eval.ZoneMap {
		if zi.Zone == ZoneSafe {
			continue
		}
		// Use the safest (lowest ordinal risk) value
		vals := AllValues[dim]
		if len(vals) > 0 {
			fixed.SetValue(dim, vals[0]) // index 0 = safest
		}
	}

	// Apply all required values
	for _, restriction := range eval.Restrictions {
		for dim, val := range restriction.Required {
			fixed.SetValue(dim, val)
		}
	}

	return &OptimizedVariant{Config: fixed}
}

// enhancedFix fixes violations AND proactively resolves restrictions.
func (o *Optimizer) enhancedFix(desired *DimensionConfig, eval *EvaluationResult) *OptimizedVariant {
	fixed := desired.Clone()

	// Fix all non-SAFE dimensions
	for dim, zi := range eval.ZoneMap {
		if zi.Zone == ZoneSafe {
			continue
		}
		if len(zi.AllowedValues) > 0 {
			fixed.SetValue(dim, zi.AllowedValues[0])
		} else {
			o.fixDimension(&fixed, dim, eval)
		}
	}

	// Apply required values
	for _, restriction := range eval.Restrictions {
		for dim, val := range restriction.Required {
			fixed.SetValue(dim, val)
		}
	}

	// Re-evaluate to converge
	for i := 0; i < 3; i++ {
		reEval := o.evaluator.Evaluate(&fixed)
		if reEval.IsCompliant {
			break
		}
		for _, restriction := range reEval.Restrictions {
			for dim, val := range restriction.Required {
				fixed.SetValue(dim, val)
			}
		}
	}

	return &OptimizedVariant{Config: fixed}
}

// fixDimension steps the dimension to the nearest safer value.
func (o *Optimizer) fixDimension(config *DimensionConfig, dim string, eval *EvaluationResult) {
	vals := AllValues[dim]
	if len(vals) == 0 {
		return
	}
	current := config.GetValue(dim)
	currentIdx := indexOf(vals, current)
	if currentIdx < 0 {
		config.SetValue(dim, vals[0])
		return
	}

	// For risk-ordered dimensions, step toward the safer end (lower index).
	// For inverse dimensions (human_in_loop, explainability), lower index = more safe.
	if currentIdx > 0 {
		config.SetValue(dim, vals[currentIdx-1])
	}
}

func (o *Optimizer) scoreVariant(original, variant *DimensionConfig, eval *EvaluationResult) OptimizedVariant {
	deltas := original.Diff(variant)
	safety := ComputeSafetyScore(eval)
	utility := ComputeUtilityScore(original, variant)
	composite := ComputeCompositeScore(safety, utility, o.weights)
	return OptimizedVariant{
		Config:         *variant,
		Evaluation:     eval,
		Deltas:         deltas,
		DeltaCount:     len(deltas),
		SafetyScore:    safety,
		UtilityScore:   utility,
		CompositeScore: composite,
	}
}

func indexOf(slice []string, val string) int {
	for i, v := range slice {
		if v == val {
			return i
		}
	}
	return -1
}

func deduplicateVariants(variants []OptimizedVariant) []OptimizedVariant {
	seen := make(map[string]bool)
	var unique []OptimizedVariant
	for _, v := range variants {
		key := configKey(&v.Config)
		if !seen[key] {
			seen[key] = true
			unique = append(unique, v)
		}
	}
	return unique
}

func configKey(c *DimensionConfig) string {
	var key string
	for _, dim := range allDimensions {
		key += dim + "=" + c.GetValue(dim) + ";"
	}
	return key
}
