package decimal

import (
	"math/big"

	"github.com/tech/sendico/pkg/merrors"
)

// Money represents a monetary amount with currency and precision handling
type Money struct {
	Amount   *big.Rat
	Currency string
	Scale    uint32 // decimal places for this currency
}

// NewMoney creates a new Money value from a decimal string
func NewMoney(amount, currency string, scale uint32) (*Money, error) {
	rat, err := RatFromString(amount)
	if err != nil {
		return nil, err
	}
	return &Money{
		Amount:   rat,
		Currency: currency,
		Scale:    scale,
	}, nil
}

// Add adds two money values (must have same currency)
func (m *Money) Add(other *Money) (*Money, error) {
	if m.Currency != other.Currency {
		return nil, merrors.InvalidArgument("decimal: currency mismatch: " + m.Currency + " != " + other.Currency)
	}
	return &Money{
		Amount:   AddRat(m.Amount, other.Amount),
		Currency: m.Currency,
		Scale:    m.Scale,
	}, nil
}

// Sub subtracts two money values (must have same currency)
func (m *Money) Sub(other *Money) (*Money, error) {
	if m.Currency != other.Currency {
		return nil, merrors.InvalidArgument("decimal: currency mismatch: " + m.Currency + " != " + other.Currency)
	}
	return &Money{
		Amount:   SubRat(m.Amount, other.Amount),
		Currency: m.Currency,
		Scale:    m.Scale,
	}, nil
}

// Mul multiplies money by a rational number (for fees, percentages, etc.)
func (m *Money) Mul(factor *big.Rat) *Money {
	return &Money{
		Amount:   MulRat(m.Amount, factor),
		Currency: m.Currency,
		Scale:    m.Scale,
	}
}

// Div divides money by a rational number
func (m *Money) Div(divisor *big.Rat) (*Money, error) {
	result, err := DivRat(m.Amount, divisor)
	if err != nil {
		return nil, err
	}
	return &Money{
		Amount:   result,
		Currency: m.Currency,
		Scale:    m.Scale,
	}, nil
}

// Negate returns the negative of this money value
func (m *Money) Negate() *Money {
	return &Money{
		Amount:   NegRat(m.Amount),
		Currency: m.Currency,
		Scale:    m.Scale,
	}
}

// Round rounds the money to its scale using the specified rounding mode
func (m *Money) Round(mode RoundingMode) (*Money, error) {
	rounded, err := RoundRatToScale(m.Amount, m.Scale, mode)
	if err != nil {
		return nil, err
	}
	return &Money{
		Amount:   rounded,
		Currency: m.Currency,
		Scale:    m.Scale,
	}, nil
}

// String returns the formatted money value
func (m *Money) String() string {
	return FormatRat(m.Amount, m.Scale) + " " + m.Currency
}

// StringAmount returns just the formatted amount
func (m *Money) StringAmount() string {
	return FormatRat(m.Amount, m.Scale)
}

// IsZero checks if the money amount is zero
func (m *Money) IsZero() bool {
	return IsZero(m.Amount)
}

// IsPositive checks if the money amount is positive
func (m *Money) IsPositive() bool {
	return IsPositive(m.Amount)
}

// IsNegative checks if the money amount is negative
func (m *Money) IsNegative() bool {
	return IsNegative(m.Amount)
}

// Cmp compares two money values (must have same currency)
// Returns -1 if m < other, 0 if m == other, 1 if m > other
func (m *Money) Cmp(other *Money) (int, error) {
	if m.Currency != other.Currency {
		return 0, merrors.InvalidArgument("decimal: currency mismatch: " + m.Currency + " != " + other.Currency)
	}
	return CmpRat(m.Amount, other.Amount), nil
}
