package maximizer

// Zone classifies a dimension value's regulatory status.
type Zone string

const (
	ZoneForbidden  Zone = "FORBIDDEN"
	ZoneRestricted Zone = "RESTRICTED"
	ZoneSafe       Zone = "SAFE"
)

// ZoneInfo classifies a single dimension value within the constraint space.
type ZoneInfo struct {
	Dimension       string   `json:"dimension"`
	CurrentValue    string   `json:"current_value"`
	Zone            Zone     `json:"zone"`
	AllowedValues   []string `json:"allowed_values,omitempty"`
	ForbiddenValues []string `json:"forbidden_values,omitempty"`
	Safeguards      []string `json:"safeguards,omitempty"`
	Reason          string   `json:"reason"`
	ObligationRefs  []string `json:"obligation_refs"`
}

// Violation is a hard block triggered by a constraint rule.
type Violation struct {
	RuleID       string `json:"rule_id"`
	ObligationID string `json:"obligation_id"`
	ArticleRef   string `json:"article_ref"`
	Title        string `json:"title"`
	Description  string `json:"description"`
	Dimension    string `json:"dimension,omitempty"`
}

// Restriction is a safeguard requirement (yellow zone).
type Restriction struct {
	RuleID       string            `json:"rule_id"`
	ObligationID string            `json:"obligation_id"`
	ArticleRef   string            `json:"article_ref"`
	Title        string            `json:"title"`
	Required     map[string]string `json:"required"`
}

// TriggeredConstraint records which constraint rule was triggered and why.
type TriggeredConstraint struct {
	RuleID       string `json:"rule_id"`
	ObligationID string `json:"obligation_id"`
	Regulation   string `json:"regulation"`
	ArticleRef   string `json:"article_ref"`
	Title        string `json:"title"`
	RuleType     string `json:"rule_type"`
}

// EvaluationResult is the complete 3-zone analysis of a DimensionConfig.
type EvaluationResult struct {
	IsCompliant        bool                    `json:"is_compliant"`
	Violations         []Violation             `json:"violations"`
	Restrictions       []Restriction           `json:"restrictions"`
	ZoneMap            map[string]ZoneInfo     `json:"zone_map"`
	RequiredControls   []string                `json:"required_controls"`
	RequiredPatterns   []string                `json:"required_patterns"`
	TriggeredRules     []TriggeredConstraint   `json:"triggered_rules"`
	RiskClassification string                  `json:"risk_classification,omitempty"`
}

// Evaluator evaluates dimension configs against constraint rules.
type Evaluator struct {
	rules *ConstraintRuleSet
}

// NewEvaluator creates an evaluator from a loaded constraint ruleset.
func NewEvaluator(rules *ConstraintRuleSet) *Evaluator {
	return &Evaluator{rules: rules}
}

// Evaluate checks a config against all constraints and produces a 3-zone result.
func (e *Evaluator) Evaluate(config *DimensionConfig) *EvaluationResult {
	result := &EvaluationResult{
		IsCompliant:      true,
		ZoneMap:          make(map[string]ZoneInfo),
		RequiredControls: []string{},
		RequiredPatterns: []string{},
	}

	// Initialize all dimensions as SAFE
	for _, dim := range allDimensions {
		result.ZoneMap[dim] = ZoneInfo{
			Dimension:    dim,
			CurrentValue: config.GetValue(dim),
			Zone:         ZoneSafe,
		}
	}

	// Evaluate each rule
	for _, rule := range e.rules.Rules {
		e.evaluateRule(config, &rule, result)
	}

	// Apply risk classification if set
	if result.RiskClassification == "" {
		result.RiskClassification = string(config.RiskClassification)
	}

	return result
}

func (e *Evaluator) evaluateRule(config *DimensionConfig, rule *ConstraintRule, result *EvaluationResult) {
	for _, constraint := range rule.Constraints {
		if !constraint.If.Matches(config) {
			continue
		}

		// Rule triggered
		result.TriggeredRules = append(result.TriggeredRules, TriggeredConstraint{
			RuleID:       rule.ID,
			ObligationID: rule.ObligationID,
			Regulation:   rule.Regulation,
			ArticleRef:   rule.ArticleRef,
			Title:        rule.Title,
			RuleType:     rule.RuleType,
		})

		// Hard block?
		if constraint.Then.Allowed != nil && !*constraint.Then.Allowed {
			result.IsCompliant = false
			result.Violations = append(result.Violations, Violation{
				RuleID:       rule.ID,
				ObligationID: rule.ObligationID,
				ArticleRef:   rule.ArticleRef,
				Title:        rule.Title,
				Description:  rule.Description,
			})
			e.markForbiddenDimensions(config, constraint.If, rule, result)
			continue
		}

		// Required values (yellow zone)?
		if len(constraint.Then.RequiredValues) > 0 {
			e.applyRequiredValues(config, constraint.Then.RequiredValues, rule, result)
		}

		// Risk classification override
		if constraint.Then.SetRiskClassification != "" {
			result.RiskClassification = constraint.Then.SetRiskClassification
		}

		// Collect controls and patterns
		result.RequiredControls = appendUnique(result.RequiredControls, constraint.Then.RequiredControls...)
		result.RequiredPatterns = appendUnique(result.RequiredPatterns, constraint.Then.RequiredPatterns...)
	}
}

// markForbiddenDimensions marks the dimensions from the condition as FORBIDDEN.
func (e *Evaluator) markForbiddenDimensions(
	config *DimensionConfig, cond ConditionSet, rule *ConstraintRule, result *EvaluationResult,
) {
	for dim := range cond {
		zi := result.ZoneMap[dim]
		zi.Zone = ZoneForbidden
		zi.Reason = rule.Title
		zi.ObligationRefs = appendUnique(zi.ObligationRefs, rule.ArticleRef)
		zi.ForbiddenValues = appendUnique(zi.ForbiddenValues, config.GetValue(dim))
		result.ZoneMap[dim] = zi
	}
}

// applyRequiredValues checks if required dimension values are met.
func (e *Evaluator) applyRequiredValues(
	config *DimensionConfig, required map[string]string, rule *ConstraintRule, result *EvaluationResult,
) {
	unmet := make(map[string]string)
	for dim, requiredVal := range required {
		actual := config.GetValue(dim)
		if actual != requiredVal {
			unmet[dim] = requiredVal
			// Mark as RESTRICTED (upgrade from SAFE, but don't downgrade from FORBIDDEN)
			zi := result.ZoneMap[dim]
			if zi.Zone != ZoneForbidden {
				zi.Zone = ZoneRestricted
				zi.Reason = rule.Title
				zi.AllowedValues = appendUnique(zi.AllowedValues, requiredVal)
				zi.Safeguards = appendUnique(zi.Safeguards, rule.ArticleRef)
				zi.ObligationRefs = appendUnique(zi.ObligationRefs, rule.ArticleRef)
				result.ZoneMap[dim] = zi
			}
		}
	}
	if len(unmet) > 0 {
		result.IsCompliant = false
		result.Restrictions = append(result.Restrictions, Restriction{
			RuleID:       rule.ID,
			ObligationID: rule.ObligationID,
			ArticleRef:   rule.ArticleRef,
			Title:        rule.Title,
			Required:     unmet,
		})
	}
}

var allDimensions = []string{
	"automation_level", "decision_binding", "decision_impact", "domain",
	"data_type", "human_in_loop", "explainability", "risk_classification",
	"legal_basis", "transparency_required", "logging_required",
	"model_type", "deployment_scope",
}

func appendUnique(slice []string, items ...string) []string {
	seen := make(map[string]bool, len(slice))
	for _, s := range slice {
		seen[s] = true
	}
	for _, item := range items {
		if item != "" && !seen[item] {
			slice = append(slice, item)
			seen[item] = true
		}
	}
	return slice
}
