package cart

import (
	"encoding/json"
	"strconv"
)

func GetTaxAmount(total int64, tax int) int64 {
	taxD := 10000 / float64(tax)
	return int64(float64(total) / float64((1 + taxD)))
}

type Price struct {
	IncVat   int64             `json:"incVat"`
	VatRates map[float32]int64 `json:"vat,omitempty"`
}

func NewPrice() *Price {
	return &Price{
		IncVat:   0,
		VatRates: make(map[float32]int64),
	}
}

func NewPriceFromIncVat(incVat int64, taxRate float32) *Price {
	tax := GetTaxAmount(incVat, int(taxRate*100))
	return &Price{
		IncVat: incVat,
		VatRates: map[float32]int64{
			taxRate: tax,
		},
	}
}

func (p *Price) ValueExVat() int64 {
	exVat := p.IncVat
	for _, amount := range p.VatRates {
		exVat -= amount
	}
	return exVat
}

func (p *Price) TotalVat() int64 {
	total := int64(0)
	for _, amount := range p.VatRates {
		total += amount
	}
	return total
}

func MultiplyPrice(p Price, qty int64) *Price {
	ret := &Price{
		IncVat:   p.IncVat * qty,
		VatRates: make(map[float32]int64),
	}
	for rate, amount := range p.VatRates {
		ret.VatRates[rate] = amount * qty
	}
	return ret
}

func (p *Price) Multiply(qty int64) {
	p.IncVat *= qty
	for rate, amount := range p.VatRates {
		p.VatRates[rate] = amount * qty
	}
}

func (p Price) MarshalJSON() ([]byte, error) {
	// Build a stable wire format without calling Price.MarshalJSON recursively
	exVat := p.ValueExVat()
	var vat map[string]int64
	if len(p.VatRates) > 0 {
		vat = make(map[string]int64, len(p.VatRates))
		for rate, amount := range p.VatRates {
			// Rely on default formatting that trims trailing zeros for whole numbers
			// Using %g could output scientific notation for large numbers; float32 rates here are small.
			key := trimFloat(rate)
			vat[key] = amount
		}
	}
	type wire struct {
		ExVat  int64            `json:"exVat"`
		IncVat int64            `json:"incVat"`
		Vat    map[string]int64 `json:"vat,omitempty"`
	}
	return json.Marshal(wire{ExVat: exVat, IncVat: p.IncVat, Vat: vat})
}

// trimFloat converts a float32 tax rate like 25 or 12.5 into a compact string without
// unnecessary decimals ("25", "12.5").
func trimFloat(f float32) string {
	// Convert via FormatFloat then trim trailing zeros and dot.
	s := strconv.FormatFloat(float64(f), 'f', -1, 32)
	return s
}

func (p *Price) Add(price Price) {
	p.IncVat += price.IncVat
	for rate, amount := range price.VatRates {
		p.VatRates[rate] += amount
	}
}

func (p *Price) Subtract(price Price) {
	p.IncVat -= price.IncVat
	for rate, amount := range price.VatRates {
		p.VatRates[rate] -= amount
	}
}

func SumPrices(prices ...Price) *Price {
	if len(prices) == 0 {
		return NewPrice()
	}

	aggregated := NewPrice()

	for _, price := range prices {
		aggregated.IncVat += price.IncVat
		for rate, amount := range price.VatRates {
			aggregated.VatRates[rate] += amount
		}
	}

	if len(aggregated.VatRates) == 0 {
		aggregated.VatRates = nil
	}

	return aggregated
}
