package identity

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"
	"unicode/utf8"

	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

type prettyRuleConfig struct {
	// Digits 限制数字规则可使用的数字，例如 ["6","8","9"]；为空时使用 0-9。
	Digits []string `json:"digits"`
	// ExcludeDigits 用于排除 4 等运营常见不投放数字，排除后再进入规则组合。
	ExcludeDigits []string `json:"exclude_digits"`
	// Values 是 custom_values 的固定候选，适合导入运营已经挑好的靓号。
	Values []string `json:"values"`
	// Pattern 是 custom_pattern 的模式，例如 AABBC、AAAABBBB；同一字母代表同一数字。
	Pattern string `json:"pattern"`
	// Prefix/Suffix 可把数字规则包装成 VIP888、888HY 等字符串靓号。
	Prefix string `json:"prefix"`
	Suffix string `json:"suffix"`
	// YearFrom/YearTo 控制日期号范围，未填写时覆盖常见生日和纪念日区间。
	YearFrom int `json:"year_from"`
	YearTo   int `json:"year_to"`
	// StartDate/EndDate 用于精确限制日期号范围，格式固定为 yyyy-mm-dd。
	StartDate string `json:"start_date"`
	EndDate   string `json:"end_date"`
}

func generatePrettyDisplayIDCandidates(ruleType string, configJSON string) ([]string, error) {
	config, err := parsePrettyRuleConfig(configJSON)
	if err != nil {
		return nil, err
	}
	digits, err := config.availableDigits()
	if err != nil {
		return nil, err
	}

	var raw []string
	switch ruleType {
	case userdomain.PrettyDisplayIDRuleAA:
		raw = repeatDigitCandidates(digits, 2)
	case userdomain.PrettyDisplayIDRuleAAA:
		raw = repeatDigitCandidates(digits, 3)
	case userdomain.PrettyDisplayIDRuleAAAA:
		raw = repeatDigitCandidates(digits, 4)
	case userdomain.PrettyDisplayIDRuleAAAAA:
		raw = repeatDigitCandidates(digits, 5)
	case userdomain.PrettyDisplayIDRuleAAAAAA:
		raw = repeatDigitCandidates(digits, 6)
	case userdomain.PrettyDisplayIDRuleAAAAAAA:
		raw = repeatDigitCandidates(digits, 7)
	case userdomain.PrettyDisplayIDRuleAABB:
		raw = patternDigitCandidates("aabb", digits)
	case userdomain.PrettyDisplayIDRuleAABBCC:
		// 旧规则按 000000-999999 生成 1000 个候选，保留 A/B/C 可相同的兼容语义。
		raw = patternCandidates("aabbcc", digits, false)
	case userdomain.PrettyDisplayIDRuleAABBCCDD:
		raw = patternDigitCandidates("aabbccdd", digits)
	case userdomain.PrettyDisplayIDRuleABAB:
		raw = patternDigitCandidates("abab", digits)
	case userdomain.PrettyDisplayIDRuleABABAB:
		raw = patternDigitCandidates("ababab", digits)
	case userdomain.PrettyDisplayIDRuleABBA:
		raw = patternDigitCandidates("abba", digits)
	case userdomain.PrettyDisplayIDRuleABCBA:
		raw = patternDigitCandidates("abcba", digits)
	case userdomain.PrettyDisplayIDRuleABCCBA:
		raw = patternDigitCandidates("abccba", digits)
	case userdomain.PrettyDisplayIDRuleAAAB:
		raw = patternDigitCandidates("aaab", digits)
	case userdomain.PrettyDisplayIDRuleAAAAB:
		raw = patternDigitCandidates("aaaab", digits)
	case userdomain.PrettyDisplayIDRuleAAAAAB:
		raw = patternDigitCandidates("aaaaab", digits)
	case userdomain.PrettyDisplayIDRuleAAABBB:
		raw = patternDigitCandidates("aaabbb", digits)
	case userdomain.PrettyDisplayIDRuleAAAABBBB:
		raw = patternDigitCandidates("aaaabbbb", digits)
	case userdomain.PrettyDisplayIDRuleABC:
		raw = sequenceDigitCandidates(digits, 3, false)
	case userdomain.PrettyDisplayIDRuleABCD:
		raw = sequenceDigitCandidates(digits, 4, false)
	case userdomain.PrettyDisplayIDRuleABCDE:
		raw = sequenceDigitCandidates(digits, 5, false)
	case userdomain.PrettyDisplayIDRuleABCDEF:
		raw = sequenceDigitCandidates(digits, 6, false)
	case userdomain.PrettyDisplayIDRuleCBA:
		raw = sequenceDigitCandidates(digits, 3, true)
	case userdomain.PrettyDisplayIDRuleDCBA:
		raw = sequenceDigitCandidates(digits, 4, true)
	case userdomain.PrettyDisplayIDRuleEDCBA:
		raw = sequenceDigitCandidates(digits, 5, true)
	case userdomain.PrettyDisplayIDRuleFEDCBA:
		raw = sequenceDigitCandidates(digits, 6, true)
	case userdomain.PrettyDisplayIDRuleABCABC:
		raw = repeatSequenceCandidates(sequenceDigitCandidates(digits, 3, false), 2)
	case userdomain.PrettyDisplayIDRuleDateYYYYMMDD:
		raw, err = dateCandidates(config, "20060102")
	case userdomain.PrettyDisplayIDRuleDateYYMMDD:
		raw, err = dateCandidates(config, "060102")
	case userdomain.PrettyDisplayIDRuleLove:
		raw = []string{"520", "521", "1314", "920", "921", "1711", "9421", "3344", "5201314", "5211314", "1314520", "1314521"}
	case userdomain.PrettyDisplayIDRuleCustomValues:
		raw = config.Values
	case userdomain.PrettyDisplayIDRuleCustomPattern:
		raw, err = customPatternCandidates(config.Pattern, digits)
	default:
		return nil, xerr.New(xerr.InvalidArgument, "rule_type is invalid")
	}
	if err != nil {
		return nil, err
	}
	candidates := finalizePrettyCandidates(raw, config)
	if len(candidates) == 0 {
		return nil, xerr.New(xerr.InvalidArgument, "pretty display id rule has no candidates")
	}
	return candidates, nil
}

func parsePrettyRuleConfig(configJSON string) (prettyRuleConfig, error) {
	var config prettyRuleConfig
	if strings.TrimSpace(configJSON) == "" {
		return config, nil
	}
	if err := json.Unmarshal([]byte(configJSON), &config); err != nil {
		return prettyRuleConfig{}, xerr.New(xerr.InvalidArgument, "rule_config_json is invalid")
	}
	config.Pattern = strings.TrimSpace(config.Pattern)
	config.Prefix = strings.TrimSpace(config.Prefix)
	config.Suffix = strings.TrimSpace(config.Suffix)
	config.StartDate = strings.TrimSpace(config.StartDate)
	config.EndDate = strings.TrimSpace(config.EndDate)
	return config, nil
}

func (c prettyRuleConfig) availableDigits() ([]string, error) {
	digits := c.Digits
	if len(digits) == 0 {
		digits = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
	}
	excluded := make(map[string]struct{}, len(c.ExcludeDigits))
	for _, digit := range c.ExcludeDigits {
		digit = strings.TrimSpace(digit)
		if digit != "" {
			excluded[digit] = struct{}{}
		}
	}
	result := make([]string, 0, len(digits))
	seen := make(map[string]struct{}, len(digits))
	for _, digit := range digits {
		digit = strings.TrimSpace(digit)
		if !validASCIIDigit(digit) {
			return nil, xerr.New(xerr.InvalidArgument, "rule_config_json digits is invalid")
		}
		if _, skip := excluded[digit]; skip {
			continue
		}
		if _, ok := seen[digit]; ok {
			continue
		}
		seen[digit] = struct{}{}
		result = append(result, digit)
	}
	if len(result) == 0 {
		return nil, xerr.New(xerr.InvalidArgument, "rule_config_json digits is empty")
	}
	return result, nil
}

func validASCIIDigit(value string) bool {
	return len(value) == 1 && value[0] >= '0' && value[0] <= '9'
}

func repeatDigitCandidates(digits []string, count int) []string {
	candidates := make([]string, 0, len(digits))
	for _, digit := range digits {
		candidates = append(candidates, strings.Repeat(digit, count))
	}
	return candidates
}

func patternDigitCandidates(pattern string, digits []string) []string {
	return patternCandidates(pattern, digits, true)
}

func patternCandidates(pattern string, digits []string, distinct bool) []string {
	variables := make([]rune, 0)
	seenVariable := make(map[rune]struct{})
	for _, r := range pattern {
		if _, ok := seenVariable[r]; ok {
			continue
		}
		seenVariable[r] = struct{}{}
		variables = append(variables, r)
	}

	assignments := make(map[rune]string, len(variables))
	usedDigits := make(map[string]struct{}, len(variables))
	candidates := make([]string, 0)
	var visit func(index int)
	visit = func(index int) {
		if index == len(variables) {
			var builder strings.Builder
			for _, r := range pattern {
				builder.WriteString(assignments[r])
			}
			candidates = append(candidates, builder.String())
			return
		}
		variable := variables[index]
		for _, digit := range digits {
			if distinct {
				if _, used := usedDigits[digit]; used {
					continue
				}
			}
			assignments[variable] = digit
			usedDigits[digit] = struct{}{}
			visit(index + 1)
			delete(assignments, variable)
			delete(usedDigits, digit)
		}
	}
	visit(0)
	return candidates
}

func sequenceDigitCandidates(digits []string, length int, descending bool) []string {
	allowed := make(map[string]struct{}, len(digits))
	for _, digit := range digits {
		allowed[digit] = struct{}{}
	}
	candidates := make([]string, 0)
	for start := 0; start <= 9; start++ {
		end := start + length - 1
		if end > 9 {
			break
		}
		var builder strings.Builder
		valid := true
		for offset := 0; offset < length; offset++ {
			value := start + offset
			if descending {
				value = end - offset
			}
			digit := fmt.Sprintf("%d", value)
			if _, ok := allowed[digit]; !ok {
				valid = false
				break
			}
			builder.WriteString(digit)
		}
		if valid {
			candidates = append(candidates, builder.String())
		}
	}
	return candidates
}

func repeatSequenceCandidates(values []string, count int) []string {
	candidates := make([]string, 0, len(values))
	for _, value := range values {
		candidates = append(candidates, strings.Repeat(value, count))
	}
	return candidates
}

func dateCandidates(config prettyRuleConfig, layout string) ([]string, error) {
	start, end, err := dateRange(config)
	if err != nil {
		return nil, err
	}
	candidates := make([]string, 0)
	for current := start; !current.After(end); current = current.AddDate(0, 0, 1) {
		candidates = append(candidates, current.Format(layout))
	}
	return candidates, nil
}

func dateRange(config prettyRuleConfig) (time.Time, time.Time, error) {
	if config.StartDate != "" || config.EndDate != "" {
		start, err := parseDateOrDefault(config.StartDate, time.Date(1950, 1, 1, 0, 0, 0, 0, time.UTC))
		if err != nil {
			return time.Time{}, time.Time{}, err
		}
		end, err := parseDateOrDefault(config.EndDate, time.Date(2035, 12, 31, 0, 0, 0, 0, time.UTC))
		if err != nil {
			return time.Time{}, time.Time{}, err
		}
		if end.Before(start) {
			return time.Time{}, time.Time{}, xerr.New(xerr.InvalidArgument, "rule_config_json date range is invalid")
		}
		return start, end, nil
	}

	yearFrom := config.YearFrom
	yearTo := config.YearTo
	if yearFrom == 0 {
		yearFrom = 1950
	}
	if yearTo == 0 {
		yearTo = 2035
	}
	if yearFrom < 1900 || yearTo > 2099 || yearTo < yearFrom {
		return time.Time{}, time.Time{}, xerr.New(xerr.InvalidArgument, "rule_config_json year range is invalid")
	}
	return time.Date(yearFrom, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(yearTo, 12, 31, 0, 0, 0, 0, time.UTC), nil
}

func parseDateOrDefault(value string, fallback time.Time) (time.Time, error) {
	if value == "" {
		return fallback, nil
	}
	parsed, err := time.ParseInLocation("2006-01-02", value, time.UTC)
	if err != nil {
		return time.Time{}, xerr.New(xerr.InvalidArgument, "rule_config_json date is invalid")
	}
	return parsed, nil
}

func customPatternCandidates(pattern string, digits []string) ([]string, error) {
	if pattern == "" || utf8.RuneCountInString(pattern) > 16 {
		return nil, xerr.New(xerr.InvalidArgument, "rule_config_json pattern is invalid")
	}
	normalized := strings.ToLower(pattern)
	for _, r := range normalized {
		if r >= 'a' && r <= 'z' {
			continue
		}
		return nil, xerr.New(xerr.InvalidArgument, "rule_config_json pattern is invalid")
	}
	return patternCandidates(normalized, digits, true), nil
}

func finalizePrettyCandidates(raw []string, config prettyRuleConfig) []string {
	candidates := make([]string, 0, len(raw))
	seen := make(map[string]struct{}, len(raw))
	for _, value := range raw {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		value = config.Prefix + value + config.Suffix
		if !userdomain.ValidAdminPrettyDisplayUserID(value) {
			continue
		}
		if _, ok := seen[value]; ok {
			continue
		}
		seen[value] = struct{}{}
		candidates = append(candidates, value)
	}
	return candidates
}

func generateAABBCCCandidates() []string {
	// AABBCC 历史规则一共 10*10*10=1000 个候选，包含 00、01 等前导零组合，靓号内容允许字符串形式保留前导零。
	return patternCandidates("aabbcc", []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, false)
}
