package handlers

import (
	"errors"
	"net/http"
	"strings"

	"git.mchus.pro/mchus/quoteforge/internal/localdb"
	"git.mchus.pro/mchus/quoteforge/internal/repository"
	"git.mchus.pro/mchus/quoteforge/internal/services"
	"github.com/gin-gonic/gin"
)

// VendorSpecHandler handles vendor BOM spec operations for a configuration.
type VendorSpecHandler struct {
	localDB       *localdb.LocalDB
	configService *services.LocalConfigurationService
}

func NewVendorSpecHandler(localDB *localdb.LocalDB) *VendorSpecHandler {
	return &VendorSpecHandler{
		localDB:       localDB,
		configService: services.NewLocalConfigurationService(localDB, nil, nil, func() bool { return false }),
	}
}

// lookupConfig finds an active configuration by UUID using the standard localDB method.
func (h *VendorSpecHandler) lookupConfig(uuid string) (*localdb.LocalConfiguration, error) {
	cfg, err := h.localDB.GetConfigurationByUUID(uuid)
	if err != nil {
		return nil, err
	}
	if !cfg.IsActive {
		return nil, errors.New("not active")
	}
	return cfg, nil
}

// GetVendorSpec returns the vendor spec (BOM) for a configuration.
// GET /api/configs/:uuid/vendor-spec
func (h *VendorSpecHandler) GetVendorSpec(c *gin.Context) {
	cfg, err := h.lookupConfig(c.Param("uuid"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
		return
	}

	spec := cfg.VendorSpec
	if spec == nil {
		spec = localdb.VendorSpec{}
	}
	c.JSON(http.StatusOK, gin.H{"vendor_spec": spec})
}

// PutVendorSpec saves (replaces) the vendor spec for a configuration.
// PUT /api/configs/:uuid/vendor-spec
func (h *VendorSpecHandler) PutVendorSpec(c *gin.Context) {
	cfg, err := h.lookupConfig(c.Param("uuid"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
		return
	}

	var body struct {
		VendorSpec []localdb.VendorSpecItem `json:"vendor_spec"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		RespondError(c, http.StatusBadRequest, "invalid request", err)
		return
	}

	for i := range body.VendorSpec {
		if body.VendorSpec[i].SortOrder == 0 {
			body.VendorSpec[i].SortOrder = (i + 1) * 10
		}
		// Persist canonical LOT mapping only.
		body.VendorSpec[i].LotMappings = normalizeLotMappings(body.VendorSpec[i].LotMappings)
		body.VendorSpec[i].ResolvedLotName = ""
		body.VendorSpec[i].ResolutionSource = ""
		body.VendorSpec[i].ManualLotSuggestion = ""
		body.VendorSpec[i].LotQtyPerPN = 0
		body.VendorSpec[i].LotAllocations = nil
	}

	spec := localdb.VendorSpec(body.VendorSpec)
	if _, err := h.configService.UpdateVendorSpecNoAuth(cfg.UUID, spec); err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"vendor_spec": spec})
}

func normalizeLotMappings(in []localdb.VendorSpecLotMapping) []localdb.VendorSpecLotMapping {
	if len(in) == 0 {
		return nil
	}
	merged := make(map[string]int, len(in))
	order := make([]string, 0, len(in))
	for _, m := range in {
		lot := strings.TrimSpace(m.LotName)
		if lot == "" {
			continue
		}
		qty := m.QuantityPerPN
		if qty < 1 {
			qty = 1
		}
		if _, exists := merged[lot]; !exists {
			order = append(order, lot)
		}
		merged[lot] += qty
	}
	out := make([]localdb.VendorSpecLotMapping, 0, len(order))
	for _, lot := range order {
		out = append(out, localdb.VendorSpecLotMapping{
			LotName:       lot,
			QuantityPerPN: merged[lot],
		})
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

// ResolveVendorSpec resolves vendor PN → LOT without modifying the cart.
// POST /api/configs/:uuid/vendor-spec/resolve
func (h *VendorSpecHandler) ResolveVendorSpec(c *gin.Context) {
	if _, err := h.lookupConfig(c.Param("uuid")); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
		return
	}

	var body struct {
		VendorSpec []localdb.VendorSpecItem `json:"vendor_spec"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		RespondError(c, http.StatusBadRequest, "invalid request", err)
		return
	}

	bookRepo := repository.NewPartnumberBookRepository(h.localDB.DB())
	resolver := services.NewVendorSpecResolver(bookRepo)

	resolved, err := resolver.Resolve(body.VendorSpec)
	if err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	book, _ := bookRepo.GetActiveBook()
	aggregated, err := services.AggregateLOTs(resolved, book, bookRepo)
	if err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"resolved":   resolved,
		"aggregated": aggregated,
	})
}

// ApplyVendorSpec applies the resolved BOM to the cart (Estimate items).
// POST /api/configs/:uuid/vendor-spec/apply
func (h *VendorSpecHandler) ApplyVendorSpec(c *gin.Context) {
	cfg, err := h.lookupConfig(c.Param("uuid"))
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "configuration not found"})
		return
	}

	var body struct {
		Items []struct {
			LotName   string  `json:"lot_name"`
			Quantity  int     `json:"quantity"`
			UnitPrice float64 `json:"unit_price"`
		} `json:"items"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		RespondError(c, http.StatusBadRequest, "invalid request", err)
		return
	}

	newItems := make(localdb.LocalConfigItems, 0, len(body.Items))
	for _, it := range body.Items {
		newItems = append(newItems, localdb.LocalConfigItem{
			LotName:   it.LotName,
			Quantity:  it.Quantity,
			UnitPrice: it.UnitPrice,
		})
	}

	if _, err := h.configService.ApplyVendorSpecItemsNoAuth(cfg.UUID, newItems); err != nil {
		RespondError(c, http.StatusInternalServerError, "internal server error", err)
		return
	}

	c.JSON(http.StatusOK, gin.H{"items": newItems})
}
