package routing

import (
	"fmt"
	"strings"
)

// SupportedFields lists the RoutingContext attributes that can be tested.
var SupportedFields = []string{
	"user.role",
	"user.department",
	"request.sensitivity",
	"request.model",
	"request.use_case",
	"request.token_estimate",
}

// SupportedOperators lists the comparison operators for conditions.
var SupportedOperators = []string{
	"eq", "neq",          // equality
	"in", "nin",          // set membership
	"gte", "lte",         // ordered comparison (sensitivity ordinal, token_estimate numeric)
	"contains", "matches", // string operations
}

// IsValidField returns true if field is a known context attribute.
func IsValidField(field string) bool {
	for _, f := range SupportedFields {
		if f == field {
			return true
		}
	}
	return false
}

// IsValidOperator returns true if op is a known operator.
func IsValidOperator(op string) bool {
	for _, o := range SupportedOperators {
		if o == op {
			return true
		}
	}
	return false
}

// Evaluate returns true when the condition holds for rctx.
// Unknown fields or operators default to false (fail-safe).
func (c Condition) Evaluate(rctx *RoutingContext) bool {
	switch c.Field {
	case "user.role":
		return evalString(c.Operator, rctx.UserRole, c.Value)
	case "user.department":
		return evalString(c.Operator, rctx.Department, c.Value)
	case "request.sensitivity":
		return evalSensitivity(c.Operator, rctx.Sensitivity, c.Value)
	case "request.model":
		return evalString(c.Operator, rctx.Model, c.Value)
	case "request.use_case":
		return evalString(c.Operator, rctx.UseCase, c.Value)
	case "request.token_estimate":
		return evalInt(c.Operator, rctx.TokenEstimate, c.Value)
	default:
		return false
	}
}

// ValidateConditions checks that all conditions in a slice have valid field+operator pairs.
// Returns a descriptive error for the first invalid condition found.
func ValidateConditions(conditions []Condition) error {
	for i, c := range conditions {
		if !IsValidField(c.Field) {
			return fmt.Errorf("condition[%d]: unknown field %q", i, c.Field)
		}
		if !IsValidOperator(c.Operator) {
			return fmt.Errorf("condition[%d]: unknown operator %q", i, c.Operator)
		}
	}
	return nil
}

// ─── String field evaluator ───────────────────────────────────────────────────

func evalString(op, fieldVal string, value interface{}) bool {
	switch op {
	case "eq":
		return fieldVal == fmt.Sprintf("%v", value)
	case "neq":
		return fieldVal != fmt.Sprintf("%v", value)
	case "contains":
		return strings.Contains(fieldVal, fmt.Sprintf("%v", value))
	case "matches": // prefix match
		return strings.HasPrefix(fieldVal, fmt.Sprintf("%v", value))
	case "in":
		return inSlice(fieldVal, value)
	case "nin":
		return !inSlice(fieldVal, value)
	default:
		return false
	}
}

// ─── Sensitivity (ordinal) evaluator ─────────────────────────────────────────

func evalSensitivity(op string, fieldVal Sensitivity, value interface{}) bool {
	// in/nin don't need a single parsed level — they work on a slice directly.
	switch op {
	case "in":
		return inSensitivitySlice(fieldVal, value)
	case "nin":
		return !inSensitivitySlice(fieldVal, value)
	}

	target, ok := ParseSensitivity(fmt.Sprintf("%v", value))
	if !ok {
		return false
	}
	switch op {
	case "eq":
		return fieldVal == target
	case "neq":
		return fieldVal != target
	case "gte":
		return fieldVal >= target
	case "lte":
		return fieldVal <= target
	default:
		return false
	}
}

// ─── Integer (token_estimate) evaluator ──────────────────────────────────────

func evalInt(op string, fieldVal int, value interface{}) bool {
	target, ok := toInt(value)
	if !ok {
		return false
	}
	switch op {
	case "eq":
		return fieldVal == target
	case "neq":
		return fieldVal != target
	case "gte":
		return fieldVal >= target
	case "lte":
		return fieldVal <= target
	default:
		return false
	}
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

// inSlice returns true if s is in the JSON array represented by value.
func inSlice(s string, value interface{}) bool {
	arr, ok := toStringSlice(value)
	if !ok {
		return false
	}
	for _, v := range arr {
		if v == s {
			return true
		}
	}
	return false
}

func inSensitivitySlice(s Sensitivity, value interface{}) bool {
	arr, ok := toStringSlice(value)
	if !ok {
		return false
	}
	for _, v := range arr {
		if lv, ok2 := ParseSensitivity(v); ok2 && lv == s {
			return true
		}
	}
	return false
}

// toStringSlice tries to convert value (JSON deserialized as []interface{}) to []string.
func toStringSlice(value interface{}) ([]string, bool) {
	raw, ok := value.([]interface{})
	if !ok {
		// Also handle []string directly (from Go code, not JSON)
		if ss, ok2 := value.([]string); ok2 {
			return ss, true
		}
		return nil, false
	}
	out := make([]string, 0, len(raw))
	for _, v := range raw {
		out = append(out, fmt.Sprintf("%v", v))
	}
	return out, true
}

// toInt tries to convert a JSON-decoded value to int.
// JSON numbers arrive as float64.
func toInt(value interface{}) (int, bool) {
	switch v := value.(type) {
	case int:
		return v, true
	case int64:
		return int(v), true
	case float64:
		return int(v), true
	default:
		return 0, false
	}
}
