package decimal

import (
	"math/big"
	"strings"

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

// RatFromString parses a decimal string into a big.Rat
// Supports standard decimal notation like "123.45", "-0.001", etc.
func RatFromString(value string) (*big.Rat, error) {
	if strings.TrimSpace(value) == "" {
		return nil, merrors.InvalidArgument("decimal: empty value")
	}
	r := new(big.Rat)
	if _, ok := r.SetString(value); !ok {
		return nil, merrors.InvalidArgument("decimal: invalid decimal value: " + value)
	}
	return r, nil
}

// MulRat multiplies two rational numbers
func MulRat(a, b *big.Rat) *big.Rat {
	return new(big.Rat).Mul(a, b)
}

// DivRat divides two rational numbers
func DivRat(a, b *big.Rat) (*big.Rat, error) {
	if b.Sign() == 0 {
		return nil, merrors.InvalidArgument("decimal: division by zero")
	}
	return new(big.Rat).Quo(a, b), nil
}

// AddRat adds two rational numbers
func AddRat(a, b *big.Rat) *big.Rat {
	return new(big.Rat).Add(a, b)
}

// SubRat subtracts two rational numbers (a - b)
func SubRat(a, b *big.Rat) *big.Rat {
	return new(big.Rat).Sub(a, b)
}

// NegRat negates a rational number
func NegRat(a *big.Rat) *big.Rat {
	return new(big.Rat).Neg(a)
}

// RoundRatToScale rounds a rational number to a specific number of decimal places
// using the specified rounding mode
func RoundRatToScale(value *big.Rat, scale uint32, mode RoundingMode) (*big.Rat, error) {
	scaleFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)
	numerator := new(big.Int).Mul(new(big.Int).Set(value.Num()), scaleFactor)
	denominator := value.Denom()

	quotient := new(big.Int)
	remainder := new(big.Int)
	quotient.QuoRem(numerator, denominator, remainder)

	// No remainder, already exact
	if remainder.Sign() == 0 {
		return new(big.Rat).SetFrac(quotient, scaleFactor), nil
	}

	sign := quotient.Sign()
	absQuotient := new(big.Int).Abs(new(big.Int).Set(quotient))
	absRemainder := new(big.Int).Abs(remainder)
	absDenominator := new(big.Int).Abs(denominator)

	doubledRemainder := new(big.Int).Mul(absRemainder, big.NewInt(2))
	cmp := doubledRemainder.Cmp(absDenominator)

	shouldIncrement := false

	switch mode {
	case RoundingModeDown:
		shouldIncrement = false
	case RoundingModeHalfUp:
		if cmp >= 0 {
			shouldIncrement = true
		}
	case RoundingModeHalfEven, RoundingModeUnspecified:
		if cmp > 0 {
			shouldIncrement = true
		} else if cmp == 0 {
			// Tie: round to even
			if absQuotient.Bit(0) == 1 {
				shouldIncrement = true
			}
		}
	default:
		// Default to HALF_EVEN
		if cmp > 0 {
			shouldIncrement = true
		} else if cmp == 0 {
			if absQuotient.Bit(0) == 1 {
				shouldIncrement = true
			}
		}
	}

	if shouldIncrement {
		if sign < 0 {
			absQuotient.Add(absQuotient, big.NewInt(1))
			quotient = absQuotient.Neg(absQuotient)
		} else {
			absQuotient.Add(absQuotient, big.NewInt(1))
			quotient = absQuotient
		}
	}

	return new(big.Rat).SetFrac(quotient, scaleFactor), nil
}

// FormatRat formats a rational number as a decimal string with the specified scale
func FormatRat(r *big.Rat, scale uint32) string {
	sign := ""
	if r.Sign() < 0 {
		sign = "-"
	}

	absRat := new(big.Rat).Abs(r)
	scaleFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)
	numerator := new(big.Int).Mul(absRat.Num(), scaleFactor)
	numerator.Quo(numerator, absRat.Denom())

	intStr := numerator.String()
	if scale == 0 {
		return sign + intStr
	}

	if len(intStr) <= int(scale) {
		intStr = strings.Repeat("0", int(scale)-len(intStr)+1) + intStr
	}

	pointPos := len(intStr) - int(scale)
	return sign + intStr[:pointPos] + "." + intStr[pointPos:]
}

// CmpRat compares two rational numbers
// Returns -1 if a < b, 0 if a == b, 1 if a > b
func CmpRat(a, b *big.Rat) int {
	return a.Cmp(b)
}

// IsZero checks if a rational number is zero
func IsZero(r *big.Rat) bool {
	return r.Sign() == 0
}

// IsPositive checks if a rational number is positive
func IsPositive(r *big.Rat) bool {
	return r.Sign() > 0
}

// IsNegative checks if a rational number is negative
func IsNegative(r *big.Rat) bool {
	return r.Sign() < 0
}
