package recording

import (
	"fmt"
	"strings"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/validations"
)

func MapStocks(recordingID uint, ownerProjectFlockKandangID *uint, items []validation.Stock) []entity.RecordingStock {
	if len(items) == 0 {
		return nil
	}

	result := make([]entity.RecordingStock, 0, len(items))
	for _, item := range items {
		usagePtr := new(float64)
		*usagePtr = item.Qty
		result = append(result, entity.RecordingStock{
			RecordingId:           recordingID,
			ProductWarehouseId:    item.ProductWarehouseId,
			ProjectFlockKandangId: ownerProjectFlockKandangID,
			UsageQty:              usagePtr,
		})
	}
	return result
}

func MapDepletions(recordingID uint, items []validation.Depletion) []entity.RecordingDepletion {
	if len(items) == 0 {
		return nil
	}

	type depletionKey struct {
		ProductWarehouseID       uint
		SourceProductWarehouseID uint
	}

	aggregate := make(map[depletionKey]float64, len(items))
	for _, item := range items {
		if item.ProductWarehouseId == 0 || item.Qty == 0 {
			continue
		}
		key := depletionKey{ProductWarehouseID: item.ProductWarehouseId}
		if item.SourceProductWarehouseId != nil {
			key.SourceProductWarehouseID = *item.SourceProductWarehouseId
		}
		aggregate[key] += item.Qty
	}
	if len(aggregate) == 0 {
		return nil
	}

	result := make([]entity.RecordingDepletion, 0, len(aggregate))
	for key, qty := range aggregate {
		if qty == 0 {
			continue
		}
		var sourceWarehouseID *uint
		if key.SourceProductWarehouseID != 0 {
			sourceWarehouseID = new(uint)
			*sourceWarehouseID = key.SourceProductWarehouseID
		}
		result = append(result, entity.RecordingDepletion{
			RecordingId:              recordingID,
			ProductWarehouseId:       key.ProductWarehouseID,
			SourceProductWarehouseId: sourceWarehouseID,
			Qty:                      qty,
		})
	}
	return result
}

func MapEggs(recordingID uint, projectFlockKandangID uint, createdBy uint, items []validation.Egg) []entity.RecordingEgg {
	if len(items) == 0 {
		return nil
	}

	result := make([]entity.RecordingEgg, 0, len(items))
	for _, item := range items {
		var sourceProjectFlockKandangID *uint
		if projectFlockKandangID != 0 {
			sourceProjectFlockKandangID = new(uint)
			*sourceProjectFlockKandangID = projectFlockKandangID
		}
		result = append(result, entity.RecordingEgg{
			RecordingId:           recordingID,
			ProductWarehouseId:    item.ProductWarehouseId,
			ProjectFlockKandangId: sourceProjectFlockKandangID,
			Qty:                   item.Qty,
			Weight:                item.Weight,
			CreatedBy:             createdBy,
		})
	}
	return result
}

type EggTotals struct {
	Qty    int
	Weight float64
}

type DepletionRoute struct {
	ProductWarehouseId       uint
	SourceProductWarehouseId uint
}

func StockUsageByWarehouse(items []entity.RecordingStock) map[uint]float64 {
	return TotalsByWarehouse(items, func(stock entity.RecordingStock) (uint, float64) {
		var usage float64
		if stock.UsageQty != nil {
			usage = *stock.UsageQty
		}
		return stock.ProductWarehouseId, usage
	})
}

func StockUsageByWarehouseReq(items []validation.Stock) map[uint]float64 {
	return TotalsByWarehouse(items, func(item validation.Stock) (uint, float64) {
		return item.ProductWarehouseId, item.Qty
	})
}

func FloatMapsEqual(a, b map[uint]float64) bool {
	if len(a) != len(b) {
		return false
	}
	for key, value := range a {
		other, ok := b[key]
		if !ok || !floatNearlyEqual(value, other) {
			return false
		}
	}
	return true
}

func EggTotalsEqual(a, b map[uint]EggTotals) bool {
	if len(a) != len(b) {
		return false
	}
	for key, value := range a {
		other, ok := b[key]
		if !ok || value.Qty != other.Qty || !floatNearlyEqual(value.Weight, other.Weight) {
			return false
		}
	}
	return true
}

func DepletionRouteMapsEqual(a, b map[DepletionRoute]float64) bool {
	if len(a) != len(b) {
		return false
	}
	for key, value := range a {
		other, ok := b[key]
		if !ok || !floatNearlyEqual(value, other) {
			return false
		}
	}
	return true
}

func floatNearlyEqual(a, b float64) bool {
	return a-b <= 0.000001 && b-a <= 0.000001
}

func TotalsByWarehouse[T any](items []T, get func(T) (uint, float64)) map[uint]float64 {
	result := make(map[uint]float64)
	for _, item := range items {
		warehouseID, qty := get(item)
		result[warehouseID] += qty
	}
	return result
}

func DepletionTotalsByRoute[T any](items []T, get func(T) (uint, *uint, float64)) map[DepletionRoute]float64 {
	result := make(map[DepletionRoute]float64)
	for _, item := range items {
		productWarehouseID, sourceProductWarehouseID, qty := get(item)
		key := DepletionRoute{ProductWarehouseId: productWarehouseID}
		if sourceProductWarehouseID != nil {
			key.SourceProductWarehouseId = *sourceProductWarehouseID
		}
		result[key] += qty
	}
	return result
}

func EggTotalsByWarehouse[T any](items []T, get func(T) (uint, int, *float64)) map[uint]EggTotals {
	result := make(map[uint]EggTotals)
	for _, item := range items {
		warehouseID, qty, weightPtr := get(item)
		weight := 0.0
		if weightPtr != nil {
			weight = *weightPtr
		}
		current := result[warehouseID]
		current.Qty += qty
		current.Weight += weight
		result[warehouseID] = current
	}
	return result
}

func RecordingNote(action string, id uint) string {
	action = strings.TrimSpace(action)
	if action == "" {
		return fmt.Sprintf("Recording#%d", id)
	}
	return fmt.Sprintf("Recording-%s#%d", action, id)
}
