package cardigann

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

// ApplyFilters applies a chain of filter transformations to a value.
func ApplyFilters(val string, filters []FilterBlock) (string, error) {
	var err error
	for _, f := range filters {
		val, err = invokeFilter(val, f)
		if err != nil {
			return val, err
		}
	}
	return val, nil
}

// invokeFilter dispatches a single filter by name.
func invokeFilter(val string, f FilterBlock) (string, error) {
	switch f.Name {
	case "querystring":
		return filterQuerystring(val, f.Args)
	case "dateparse", "timeparse":
		return filterDateParse(val, f.Args)
	case "regexp":
		return filterRegexp(val, f.Args)
	case "split":
		return filterSplit(val, f.Args)
	case "replace":
		return filterReplace(val, f.Args)
	case "trim":
		return filterTrim(val, f.Args)
	case "append":
		return filterAppend(val, f.Args)
	case "prepend":
		return filterPrepend(val, f.Args)
	case "timeago", "fuzzytime", "reltime":
		return filterTimeAgo(val, f.Args)
	default:
		return val, fmt.Errorf("unknown filter: %q", f.Name)
	}
}

// filterQuerystring extracts a query parameter from a URL value.
// Args: param name string
func filterQuerystring(val string, args interface{}) (string, error) {
	paramName, ok := args.(string)
	if !ok {
		return val, fmt.Errorf("querystring filter: args must be a string")
	}

	// Find the query string part
	qIdx := strings.Index(val, "?")
	if qIdx < 0 {
		return "", nil
	}
	query := val[qIdx+1:]

	// Parse manually to avoid importing net/url for simple cases
	for _, pair := range strings.Split(query, "&") {
		kv := strings.SplitN(pair, "=", 2)
		if len(kv) == 2 && kv[0] == paramName {
			// Basic URL decoding
			result := strings.ReplaceAll(kv[1], "+", " ")
			result = strings.ReplaceAll(result, "%20", " ")
			return result, nil
		}
	}

	return "", nil
}

// filterDateParse parses a date string using a Go time layout.
// Args: layout string (e.g., "2006-01-02")
func filterDateParse(val string, args interface{}) (string, error) {
	layout, ok := args.(string)
	if !ok {
		return val, fmt.Errorf("dateparse filter: args must be a string (Go time layout)")
	}

	t, err := time.Parse(layout, strings.TrimSpace(val))
	if err != nil {
		return val, fmt.Errorf("dateparse: %w", err)
	}

	return t.Format(time.RFC3339), nil
}

// filterRegexp extracts the first capture group from value.
// Args: pattern string
func filterRegexp(val string, args interface{}) (string, error) {
	pattern, ok := args.(string)
	if !ok {
		return val, fmt.Errorf("regexp filter: args must be a string (pattern)")
	}

	re, err := regexp.Compile(pattern)
	if err != nil {
		return val, fmt.Errorf("regexp compile: %w", err)
	}

	matches := re.FindStringSubmatch(val)
	if len(matches) < 2 {
		return val, nil
	}

	return matches[1], nil
}

// filterSplit splits value by separator and returns the element at position.
// Args: [separator, position] as []interface{} or single string
func filterSplit(val string, args interface{}) (string, error) {
	sep, pos := parseSplitArgs(args)
	parts := strings.Split(val, sep)

	idx := int(pos)
	if idx < 0 {
		idx = len(parts) + idx
	}
	if idx < 0 || idx >= len(parts) {
		return val, nil
	}

	return parts[idx], nil
}

// filterReplace performs string replacement.
// Args: [from, to] as []interface{} or single string
func filterReplace(val string, args interface{}) (string, error) {
	from, to := parseReplaceArgs(args)
	return strings.ReplaceAll(val, from, to), nil
}

// filterTrim trims characters from both sides of value.
// Args: cutset string
func filterTrim(val string, args interface{}) (string, error) {
	cutset, ok := args.(string)
	if !ok {
		return strings.TrimSpace(val), nil
	}
	return strings.Trim(val, cutset), nil
}

// filterAppend appends a suffix to value.
// Args: suffix string
func filterAppend(val string, args interface{}) (string, error) {
	suffix, ok := args.(string)
	if !ok {
		return val, fmt.Errorf("append filter: args must be a string")
	}
	return val + suffix, nil
}

// filterPrepend prepends a prefix to value.
// Args: prefix string
func filterPrepend(val string, args interface{}) (string, error) {
	prefix, ok := args.(string)
	if !ok {
		return val, fmt.Errorf("prepend filter: args must be a string")
	}
	return prefix + val, nil
}

// filterTimeAgo parses relative time strings like "2 hours ago", "yesterday", "3d ago".
// It returns an RFC3339 formatted timestamp.
func filterTimeAgo(val string, _ interface{}) (string, error) {
	t, err := parseFuzzyTime(strings.TrimSpace(val))
	if err != nil {
		return val, err
	}
	return t.Format(time.RFC3339), nil
}

// parseFuzzyTime handles relative time strings.
// Supports: "N unit(s) ago", "yesterday", abbreviations like "2h ago", "3d", "1w ago".
func parseFuzzyTime(val string) (time.Time, error) {
	now := time.Now()
	lower := strings.ToLower(val)

	// Handle "yesterday"
	if lower == "yesterday" {
		return now.AddDate(0, 0, -1), nil
	}
	if lower == "today" || lower == "now" {
		return now, nil
	}

	// Remove "ago" suffix
	lower = strings.TrimSuffix(lower, " ago")
	lower = strings.TrimSuffix(lower, " ago.")
	lower = strings.TrimSpace(lower)

	// Handle just a number + unit without "ago" (e.g., "3d", "2h")
	// Pattern: optional number, then unit abbreviation or full name
	re := regexp.MustCompile(`^(\d+)\s*(s(?:ec(?:ond)?s?)?|m(?:in(?:ute)?s?)?|h(?:ou?r?s?)?|d(?:ay?s?)?|w(?:ee?k?s?)?|mo(?:nth?s?)?|y(?:ea?r?s?)?)$`)
	matches := re.FindStringSubmatch(lower)
	if len(matches) < 3 {
		// Try the pattern: "N units ago" format
		re2 := regexp.MustCompile(`^(\d+)\s+(s(?:ec(?:ond)?s?)?|m(?:in(?:ute)?s?)?|h(?:ou?r?s?)?|d(?:ay?s?)?|w(?:ee?k?s?)?|mo(?:nth?s?)?|y(?:ea?r?s?)?)$`)
		matches = re2.FindStringSubmatch(lower)
	}
	if len(matches) < 3 {
		// Try standard duration like "2 hours ago"
		re3 := regexp.MustCompile(`^(\d+)\s+(seconds?|minutes?|hours?|days?|weeks?|months?|years?)$`)
		matches = re3.FindStringSubmatch(lower)
	}

	if len(matches) < 3 {
		return now, fmt.Errorf("unrecognized relative time: %q", val)
	}

	n, err := strconv.Atoi(matches[1])
	if err != nil {
		return now, fmt.Errorf("invalid number in relative time: %q", matches[1])
	}

	unit := matches[2]
	switch {
	case strings.HasPrefix(unit, "s"):
		return now.Add(-time.Duration(n) * time.Second), nil
	case strings.HasPrefix(unit, "mi"):
		return now.Add(-time.Duration(n) * time.Minute), nil
	case strings.HasPrefix(unit, "h"):
		return now.Add(-time.Duration(n) * time.Hour), nil
	case strings.HasPrefix(unit, "d"):
		return now.AddDate(0, 0, -n), nil
	case strings.HasPrefix(unit, "w"):
		return now.AddDate(0, 0, -n*7), nil
	case strings.HasPrefix(unit, "mo"):
		return now.AddDate(0, -n, 0), nil
	case strings.HasPrefix(unit, "y"):
		return now.AddDate(-n, 0, 0), nil
	default:
		return now, fmt.Errorf("unrecognized time unit: %q", unit)
	}
}

// parseSplitArgs extracts separator and position from filter args.
// Args can be: []interface{}{sep, pos}, or a string (defaults to comma separator, position 0).
func parseSplitArgs(args interface{}) (string, int) {
	switch a := args.(type) {
	case []interface{}:
		sep := ","
		pos := 0
		if len(a) > 0 {
			if s, ok := a[0].(string); ok {
				sep = s
			}
		}
		if len(a) > 1 {
			switch p := a[1].(type) {
			case int:
				pos = p
			case float64:
				pos = int(p)
			case string:
				pos, _ = strconv.Atoi(p)
			}
		}
		return sep, pos
	case string:
		return a, 0
	default:
		return ",", 0
	}
}

// parseReplaceArgs extracts from/to from filter args.
// Args can be: []interface{}{from, to}, or a single string (empty replacement).
func parseReplaceArgs(args interface{}) (string, string) {
	switch a := args.(type) {
	case []interface{}:
		from := ""
		to := ""
		if len(a) > 0 {
			if s, ok := a[0].(string); ok {
				from = s
			}
		}
		if len(a) > 1 {
			if s, ok := a[1].(string); ok {
				to = s
			}
		}
		return from, to
	case string:
		return a, ""
	default:
		return "", ""
	}
}
