package article

import (
	"errors"
	"fmt"
	"strings"

	"git.mchus.pro/mchus/quoteforge/internal/localdb"
)

// ErrMissingCategoryForLot is returned when a lot has no category in local_pricelist_items.lot_category.
var ErrMissingCategoryForLot = errors.New("missing_category_for_lot")

type MissingCategoryForLotError struct {
	LotName string
}

func (e *MissingCategoryForLotError) Error() string {
	if e == nil || strings.TrimSpace(e.LotName) == "" {
		return ErrMissingCategoryForLot.Error()
	}
	return fmt.Sprintf("%s: %s", ErrMissingCategoryForLot.Error(), e.LotName)
}

func (e *MissingCategoryForLotError) Unwrap() error {
	return ErrMissingCategoryForLot
}

type Group string

const (
	GroupCPU  Group = "CPU"
	GroupMEM  Group = "MEM"
	GroupGPU  Group = "GPU"
	GroupDISK Group = "DISK"
	GroupNET  Group = "NET"
	GroupPSU  Group = "PSU"
)

// GroupForLotCategory maps pricelist lot_category codes into article groups.
// Unknown/unrelated categories return ok=false.
func GroupForLotCategory(cat string) (group Group, ok bool) {
	c := strings.ToUpper(strings.TrimSpace(cat))
	switch c {
	case "CPU":
		return GroupCPU, true
	case "MEM":
		return GroupMEM, true
	case "GPU":
		return GroupGPU, true
	case "M2", "SSD", "HDD", "EDSFF", "HHHL":
		return GroupDISK, true
	case "NIC", "HCA", "DPU":
		return GroupNET, true
	case "HBA":
		return GroupNET, true
	case "PSU", "PS":
		return GroupPSU, true
	default:
		return "", false
	}
}

// ResolveLotCategoriesStrict resolves categories for lotNames using local_pricelist_items.lot_category
// for a given server pricelist id. If any lot is missing or has empty category, returns an error.
func ResolveLotCategoriesStrict(local *localdb.LocalDB, serverPricelistID uint, lotNames []string) (map[string]string, error) {
	if local == nil {
		return nil, fmt.Errorf("local db is nil")
	}
	cats, err := local.GetLocalLotCategoriesByServerPricelistID(serverPricelistID, lotNames)
	if err != nil {
		return nil, err
	}
	missing := make([]string, 0)
	for _, lot := range lotNames {
		cat := strings.TrimSpace(cats[lot])
		if cat == "" {
			missing = append(missing, lot)
			continue
		}
		cats[lot] = cat
	}
	if len(missing) > 0 {
		fallback, err := local.GetLocalComponentCategoriesByLotNames(missing)
		if err != nil {
			return nil, err
		}
		for _, lot := range missing {
			if cat := strings.TrimSpace(fallback[lot]); cat != "" {
				cats[lot] = cat
			}
		}
		for _, lot := range missing {
			if strings.TrimSpace(cats[lot]) == "" {
				return nil, &MissingCategoryForLotError{LotName: lot}
			}
		}
	}
	return cats, nil
}

// NormalizeServerModel produces a stable article segment for the server model.
func NormalizeServerModel(model string) string {
	trimmed := strings.TrimSpace(model)
	if trimmed == "" {
		return ""
	}
	upper := strings.ToUpper(trimmed)
	var b strings.Builder
	for _, r := range upper {
		if r >= 'A' && r <= 'Z' {
			b.WriteRune(r)
			continue
		}
		if r >= '0' && r <= '9' {
			b.WriteRune(r)
			continue
		}
		if r == '.' {
			b.WriteRune(r)
		}
	}
	return b.String()
}
