package core

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// ValidateStructFields validates fields of a map against declarative rules.
// Each rule is a comma-separated string like "required,type=string,min=1,max=100".
//
// Supported rules:
//   - required       — field must exist and not be nil or ""
//   - type=string|int|float|bool — validate underlying Go type
//   - min=N, max=N   — for numeric values
//   - minlen=N, maxlen=N — for string values
//   - oneof=a|b|c    — value must be one of the listed options
//   - pattern=regex  — for string values
//
// Returns (valid, errors). Errors accumulate — all fields are checked.
func ValidateStructFields(data map[string]any, rules map[string]string) (bool, []string) {
	var errs []string

	for field, ruleStr := range rules {
		parts := strings.Split(ruleStr, ",")
		for _, part := range parts {
			part = strings.TrimSpace(part)
			if part == "" {
				continue
			}

			if err := applyRule(data, field, part); err != "" {
				errs = append(errs, err)
				// stop further checks on this field if required failed
				if part == "required" {
					break
				}
			}
		}
	}

	return len(errs) == 0, errs
}

// applyRule applies a single rule to a field and returns an error string or "".
func applyRule(data map[string]any, field, rule string) string {
	switch {
	case rule == "required":
		val, ok := data[field]
		if !ok || val == nil {
			return fmt.Sprintf("%s: required field missing", field)
		}
		if s, ok := val.(string); ok && s == "" {
			return fmt.Sprintf("%s: required field is empty string", field)
		}
		return ""

	case strings.HasPrefix(rule, "type="):
		expectedType := rule[len("type="):]
		val, ok := data[field]
		if !ok || val == nil {
			return "" // absence handled by required
		}
		return checkType(field, val, expectedType)

	case strings.HasPrefix(rule, "min="):
		n, err := strconv.ParseFloat(rule[len("min="):], 64)
		if err != nil {
			return fmt.Sprintf("%s: invalid rule min value: %s", field, rule)
		}
		val, ok := data[field]
		if !ok || val == nil {
			return ""
		}
		f, ok := toFloat(val)
		if !ok {
			return fmt.Sprintf("%s: cannot apply min to non-numeric value", field)
		}
		if f < n {
			return fmt.Sprintf("%s: %v < min %v", field, val, n)
		}
		return ""

	case strings.HasPrefix(rule, "max="):
		n, err := strconv.ParseFloat(rule[len("max="):], 64)
		if err != nil {
			return fmt.Sprintf("%s: invalid rule max value: %s", field, rule)
		}
		val, ok := data[field]
		if !ok || val == nil {
			return ""
		}
		f, ok := toFloat(val)
		if !ok {
			return fmt.Sprintf("%s: cannot apply max to non-numeric value", field)
		}
		if f > n {
			return fmt.Sprintf("%s: %v > max %v", field, val, n)
		}
		return ""

	case strings.HasPrefix(rule, "minlen="):
		n, err := strconv.Atoi(rule[len("minlen="):])
		if err != nil {
			return fmt.Sprintf("%s: invalid rule minlen value: %s", field, rule)
		}
		val, ok := data[field]
		if !ok || val == nil {
			return ""
		}
		s, ok := val.(string)
		if !ok {
			return fmt.Sprintf("%s: cannot apply minlen to non-string value", field)
		}
		if len(s) < n {
			return fmt.Sprintf("%s: length %d < minlen %d", field, len(s), n)
		}
		return ""

	case strings.HasPrefix(rule, "maxlen="):
		n, err := strconv.Atoi(rule[len("maxlen="):])
		if err != nil {
			return fmt.Sprintf("%s: invalid rule maxlen value: %s", field, rule)
		}
		val, ok := data[field]
		if !ok || val == nil {
			return ""
		}
		s, ok := val.(string)
		if !ok {
			return fmt.Sprintf("%s: cannot apply maxlen to non-string value", field)
		}
		if len(s) > n {
			return fmt.Sprintf("%s: length %d > maxlen %d", field, len(s), n)
		}
		return ""

	case strings.HasPrefix(rule, "oneof="):
		options := strings.Split(rule[len("oneof="):], "|")
		val, ok := data[field]
		if !ok || val == nil {
			return ""
		}
		sval := fmt.Sprintf("%v", val)
		for _, opt := range options {
			if sval == opt {
				return ""
			}
		}
		return fmt.Sprintf("%s: value %q not in oneof [%s]", field, sval, rule[len("oneof="):])

	case strings.HasPrefix(rule, "pattern="):
		pat := rule[len("pattern="):]
		val, ok := data[field]
		if !ok || val == nil {
			return ""
		}
		s, ok := val.(string)
		if !ok {
			return fmt.Sprintf("%s: cannot apply pattern to non-string value", field)
		}
		re, err := regexp.Compile(pat)
		if err != nil {
			return fmt.Sprintf("%s: invalid pattern %q: %v", field, pat, err)
		}
		if !re.MatchString(s) {
			return fmt.Sprintf("%s: value %q does not match pattern %q", field, s, pat)
		}
		return ""

	default:
		return fmt.Sprintf("%s: unknown rule %q", field, rule)
	}
}

func checkType(field string, val any, expected string) string {
	var ok bool
	switch expected {
	case "string":
		_, ok = val.(string)
	case "int":
		switch val.(type) {
		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
			ok = true
		}
	case "float":
		switch val.(type) {
		case float32, float64:
			ok = true
		case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
			ok = true // integers are valid floats
		}
	case "bool":
		_, ok = val.(bool)
	default:
		return fmt.Sprintf("%s: unknown type rule %q", field, expected)
	}
	if !ok {
		return fmt.Sprintf("%s: expected type %s, got %T", field, expected, val)
	}
	return ""
}

func toFloat(val any) (float64, bool) {
	switch v := val.(type) {
	case int:
		return float64(v), true
	case int8:
		return float64(v), true
	case int16:
		return float64(v), true
	case int32:
		return float64(v), true
	case int64:
		return float64(v), true
	case uint:
		return float64(v), true
	case uint8:
		return float64(v), true
	case uint16:
		return float64(v), true
	case uint32:
		return float64(v), true
	case uint64:
		return float64(v), true
	case float32:
		return float64(v), true
	case float64:
		return v, true
	}
	return 0, false
}
