package lotmatch

import (
	"errors"
	"regexp"
	"sort"
	"strings"

	"git.mchus.pro/mchus/quoteforge/internal/models"
	"gorm.io/gorm"
)

var (
	ErrResolveConflict = errors.New("multiple lot matches")
	ErrResolveNotFound = errors.New("lot not found")
)

type LotResolver struct {
	partnumberToLots map[string][]string
	exactLots        map[string]string
	allLots          []string
}

type MappingMatcher struct {
	exact    map[string][]string
	exactLot map[string]string
	wildcard []wildcardMapping
}

type wildcardMapping struct {
	lotName string
	re      *regexp.Regexp
}

func NewLotResolverFromDB(db *gorm.DB) (*LotResolver, error) {
	mappings, lots, err := loadMappingsAndLots(db)
	if err != nil {
		return nil, err
	}
	return NewLotResolver(mappings, lots), nil
}

func NewMappingMatcherFromDB(db *gorm.DB) (*MappingMatcher, error) {
	mappings, lots, err := loadMappingsAndLots(db)
	if err != nil {
		return nil, err
	}
	return NewMappingMatcher(mappings, lots), nil
}

func NewLotResolver(mappings []models.LotPartnumber, lots []models.Lot) *LotResolver {
	partnumberToLots := make(map[string][]string, len(mappings))
	for _, m := range mappings {
		pn := NormalizeKey(m.Partnumber)
		lot := strings.TrimSpace(m.LotName)
		if pn == "" || lot == "" {
			continue
		}
		partnumberToLots[pn] = append(partnumberToLots[pn], lot)
	}
	for key := range partnumberToLots {
		partnumberToLots[key] = uniqueCaseInsensitive(partnumberToLots[key])
	}

	exactLots := make(map[string]string, len(lots))
	allLots := make([]string, 0, len(lots))
	for _, l := range lots {
		name := strings.TrimSpace(l.LotName)
		if name == "" {
			continue
		}
		exactLots[NormalizeKey(name)] = name
		allLots = append(allLots, name)
	}
	sort.Slice(allLots, func(i, j int) bool {
		li := len([]rune(allLots[i]))
		lj := len([]rune(allLots[j]))
		if li == lj {
			return allLots[i] < allLots[j]
		}
		return li > lj
	})

	return &LotResolver{
		partnumberToLots: partnumberToLots,
		exactLots:        exactLots,
		allLots:          allLots,
	}
}

func NewMappingMatcher(mappings []models.LotPartnumber, lots []models.Lot) *MappingMatcher {
	exact := make(map[string][]string, len(mappings))
	wildcards := make([]wildcardMapping, 0, len(mappings))
	for _, m := range mappings {
		pn := NormalizeKey(m.Partnumber)
		lot := strings.TrimSpace(m.LotName)
		if pn == "" || lot == "" {
			continue
		}
		if strings.Contains(pn, "*") {
			pattern := "^" + regexp.QuoteMeta(pn) + "$"
			pattern = strings.ReplaceAll(pattern, "\\*", ".*")
			re, err := regexp.Compile(pattern)
			if err != nil {
				continue
			}
			wildcards = append(wildcards, wildcardMapping{lotName: lot, re: re})
			continue
		}
		exact[pn] = append(exact[pn], lot)
	}
	for key := range exact {
		exact[key] = uniqueCaseInsensitive(exact[key])
	}

	exactLot := make(map[string]string, len(lots))
	for _, l := range lots {
		name := strings.TrimSpace(l.LotName)
		if name == "" {
			continue
		}
		exactLot[NormalizeKey(name)] = name
	}

	return &MappingMatcher{
		exact:    exact,
		exactLot: exactLot,
		wildcard: wildcards,
	}
}

func (r *LotResolver) Resolve(partnumber string) (string, string, error) {
	key := NormalizeKey(partnumber)
	if key == "" {
		return "", "", ErrResolveNotFound
	}

	if mapped := r.partnumberToLots[key]; len(mapped) > 0 {
		if len(mapped) == 1 {
			return mapped[0], "mapping_table", nil
		}
		return "", "", ErrResolveConflict
	}
	if exact, ok := r.exactLots[key]; ok {
		return exact, "article_exact", nil
	}

	best := ""
	bestLen := -1
	tie := false
	for _, lot := range r.allLots {
		lotKey := NormalizeKey(lot)
		if lotKey == "" {
			continue
		}
		if strings.HasPrefix(key, lotKey) {
			l := len([]rune(lotKey))
			if l > bestLen {
				best = lot
				bestLen = l
				tie = false
			} else if l == bestLen && !strings.EqualFold(best, lot) {
				tie = true
			}
		}
	}
	if best == "" {
		return "", "", ErrResolveNotFound
	}
	if tie {
		return "", "", ErrResolveConflict
	}
	return best, "prefix", nil
}

func (m *MappingMatcher) MatchLots(partnumber string) []string {
	if m == nil {
		return nil
	}
	key := NormalizeKey(partnumber)
	if key == "" {
		return nil
	}

	lots := make([]string, 0, 2)
	if exact := m.exact[key]; len(exact) > 0 {
		lots = append(lots, exact...)
	}
	for _, wc := range m.wildcard {
		if wc.re == nil || !wc.re.MatchString(key) {
			continue
		}
		lots = append(lots, wc.lotName)
	}
	if lot, ok := m.exactLot[key]; ok && strings.TrimSpace(lot) != "" {
		lots = append(lots, lot)
	}
	return uniqueCaseInsensitive(lots)
}

func NormalizeKey(v string) string {
	s := strings.ToLower(strings.TrimSpace(v))
	replacer := strings.NewReplacer(" ", "", "-", "", "_", "", ".", "", "/", "", "\\", "", "\"", "", "'", "", "(", "", ")", "")
	return replacer.Replace(s)
}

func loadMappingsAndLots(db *gorm.DB) ([]models.LotPartnumber, []models.Lot, error) {
	var mappings []models.LotPartnumber
	if err := db.Find(&mappings).Error; err != nil {
		return nil, nil, err
	}
	var lots []models.Lot
	if err := db.Select("lot_name").Find(&lots).Error; err != nil {
		return nil, nil, err
	}
	return mappings, lots, nil
}

func uniqueCaseInsensitive(values []string) []string {
	seen := make(map[string]struct{}, len(values))
	out := make([]string, 0, len(values))
	for _, v := range values {
		trimmed := strings.TrimSpace(v)
		if trimmed == "" {
			continue
		}
		key := strings.ToLower(trimmed)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, trimmed)
	}
	sort.Slice(out, func(i, j int) bool {
		return strings.ToLower(out[i]) < strings.ToLower(out[j])
	})
	return out
}
