package core

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

// aliases maps cron shorthand expressions to their 5-field equivalents.
var cronAliases = map[string]string{
	"@yearly":   "0 0 1 1 *",
	"@annually": "0 0 1 1 *",
	"@monthly":  "0 0 1 * *",
	"@weekly":   "0 0 * * 0",
	"@daily":    "0 0 * * *",
	"@midnight": "0 0 * * *",
	"@hourly":   "0 * * * *",
}

// fieldLimits defines the valid [min, max] range for each cron field.
var cronFieldLimits = [5][2]int{
	{0, 59},  // minute
	{0, 23},  // hour
	{1, 31},  // day of month
	{1, 12},  // month
	{0, 6},   // day of week
}

var cronFieldNames = [5]string{"minute", "hour", "day_of_month", "month", "day_of_week"}

// ParseCronExpr parses a standard 5-field cron expression into a CronSchedule.
// Supports *, ranges (1-5), lists (1,3,5), steps (*/15), and aliases (@hourly, @daily, @weekly, @monthly, @yearly).
// Returns an error for invalid expressions or out-of-range values.
func ParseCronExpr(expr string) (CronSchedule, error) {
	expr = strings.TrimSpace(expr)

	// Resolve aliases.
	if expanded, ok := cronAliases[expr]; ok {
		expr = expanded
	}

	fields := strings.Fields(expr)
	if len(fields) != 5 {
		return CronSchedule{}, fmt.Errorf("parse_cron_expr: expected 5 fields, got %d in %q", len(fields), expr)
	}

	var result [5][]int
	for i, field := range fields {
		lo, hi := cronFieldLimits[i][0], cronFieldLimits[i][1]
		values, err := parseCronField(field, lo, hi)
		if err != nil {
			return CronSchedule{}, fmt.Errorf("parse_cron_expr: field %s: %w", cronFieldNames[i], err)
		}
		result[i] = values
	}

	return CronSchedule{
		Minute:     result[0],
		Hour:       result[1],
		DayOfMonth: result[2],
		Month:      result[3],
		DayOfWeek:  result[4],
		Raw:        strings.TrimSpace(strings.Join(fields, " ")),
	}, nil
}

// parseCronField expands a single cron field token into the list of matching integers.
func parseCronField(field string, lo, hi int) ([]int, error) {
	// Handle wildcard.
	if field == "*" {
		return rangeSlice(lo, hi), nil
	}

	var values []int
	seen := make(map[int]bool)

	// Handle comma-separated list.
	parts := strings.Split(field, ",")
	for _, part := range parts {
		expanded, err := parseCronPart(part, lo, hi)
		if err != nil {
			return nil, err
		}
		for _, v := range expanded {
			if !seen[v] {
				seen[v] = true
				values = append(values, v)
			}
		}
	}

	// Sort.
	sortInts(values)
	return values, nil
}

// parseCronPart handles a single part: plain int, range (a-b), or step (*/n or a-b/n).
func parseCronPart(part string, lo, hi int) ([]int, error) {
	// Step: */n or a-b/n
	if idx := strings.Index(part, "/"); idx != -1 {
		stepStr := part[idx+1:]
		step, err := strconv.Atoi(stepStr)
		if err != nil || step <= 0 {
			return nil, fmt.Errorf("invalid step %q", stepStr)
		}
		base := part[:idx]
		var start, end int
		if base == "*" {
			start, end = lo, hi
		} else if dashIdx := strings.Index(base, "-"); dashIdx != -1 {
			var err2 error
			start, end, err2 = parseRange(base, lo, hi)
			if err2 != nil {
				return nil, err2
			}
		} else {
			v, err2 := parseValue(base, lo, hi)
			if err2 != nil {
				return nil, err2
			}
			start, end = v, hi
		}
		var result []int
		for v := start; v <= end; v += step {
			result = append(result, v)
		}
		return result, nil
	}

	// Range: a-b
	if strings.Contains(part, "-") {
		start, end, err := parseRange(part, lo, hi)
		if err != nil {
			return nil, err
		}
		return rangeSlice(start, end), nil
	}

	// Plain integer.
	v, err := parseValue(part, lo, hi)
	if err != nil {
		return nil, err
	}
	return []int{v}, nil
}

func parseRange(s string, lo, hi int) (int, int, error) {
	parts := strings.SplitN(s, "-", 2)
	if len(parts) != 2 {
		return 0, 0, fmt.Errorf("invalid range %q", s)
	}
	start, err := parseValue(parts[0], lo, hi)
	if err != nil {
		return 0, 0, err
	}
	end, err := parseValue(parts[1], lo, hi)
	if err != nil {
		return 0, 0, err
	}
	if start > end {
		return 0, 0, fmt.Errorf("range start %d > end %d", start, end)
	}
	return start, end, nil
}

func parseValue(s string, lo, hi int) (int, error) {
	v, err := strconv.Atoi(s)
	if err != nil {
		return 0, fmt.Errorf("invalid value %q: not an integer", s)
	}
	if v < lo || v > hi {
		return 0, fmt.Errorf("value %d out of range [%d, %d]", v, lo, hi)
	}
	return v, nil
}

func rangeSlice(lo, hi int) []int {
	s := make([]int, hi-lo+1)
	for i := range s {
		s[i] = lo + i
	}
	return s
}

// sortInts is a simple insertion sort for small slices (avoids importing sort).
func sortInts(a []int) {
	for i := 1; i < len(a); i++ {
		for j := i; j > 0 && a[j] < a[j-1]; j-- {
			a[j], a[j-1] = a[j-1], a[j]
		}
	}
}
