package types

import (
	"fmt"
	"math/big"
	"strings"

	"cosmossdk.io/math"
	sdk "github.com/cosmos/cosmos-sdk/types"
	paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
)

var _ paramtypes.ParamSet = (*Params)(nil)

var (
	KeyReserveTime               = []byte("ReserveTime")
	KeyForcedSettleTime          = []byte("ForcedSettleTime")
	KeyPaymentAccountCountLimit  = []byte("PaymentAccountCountLimit")
	KeyMaxAutoSettleFlowCount    = []byte("MaxAutoSettleFlowCount")
	KeyMaxAutoResumeFlowCount    = []byte("MaxAutoResumeFlowCount")
	KeyFeeDenom                  = []byte("FeeDenom")
	KeyValidatorTaxRate          = []byte("ValidatorTaxRate")
	KeyWithdrawTimeLockThreshold = []byte("WithdrawTimeLockThreshold")
	KeyWithdrawTimeLockDuration  = []byte("WithdrawTimeLockDuration")

	DefaultReserveTime      uint64  = 180 * 24 * 60 * 60       // 180 days
	DefaultValidatorTaxRate sdk.Dec = sdk.NewDecWithPrec(1, 2) // 1%

	DefaultForcedSettleTime          uint64 = 24 * 60 * 60 // 1 day
	DefaultPaymentAccountCountLimit  uint64 = 200
	DefaultMaxAutoSettleFlowCount    uint64 = 100
	DefaultMaxAutoResumeFlowCount    uint64 = 100
	DefaultFeeDenom                  string = "BNB"
	DefaultWithdrawTimeLockThreshold        = math.NewIntFromBigInt(big.NewInt(1e18)).MulRaw(100) // 100 BNB
	DefaultWithdrawTimeLockDuration  uint64 = 24 * 60 * 60                                        // 1 day
)

// ParamKeyTable the param key table for launch module
func ParamKeyTable() paramtypes.KeyTable {
	return paramtypes.NewKeyTable().RegisterParamSet(&Params{})
}

// NewParams creates a new Params instance
func NewParams(
	reserveTime uint64,
	validatorTaxRate sdk.Dec,
	forcedSettleTime uint64,
	paymentAccountCountLimit uint64,
	MaxAutoSettleFlowCount uint64,
	maxAutoResumeFlowCount uint64,
	feeDenom string,
	withdrawTimeLockThreshold math.Int,
	withdrawTimeLockDuration uint64,
) Params {
	return Params{
		VersionedParams:           VersionedParams{ReserveTime: reserveTime, ValidatorTaxRate: validatorTaxRate},
		ForcedSettleTime:          forcedSettleTime,
		PaymentAccountCountLimit:  paymentAccountCountLimit,
		MaxAutoSettleFlowCount:    MaxAutoSettleFlowCount,
		MaxAutoResumeFlowCount:    maxAutoResumeFlowCount,
		FeeDenom:                  feeDenom,
		WithdrawTimeLockThreshold: &withdrawTimeLockThreshold,
		WithdrawTimeLockDuration:  withdrawTimeLockDuration,
	}
}

// DefaultParams returns a default set of parameters
func DefaultParams() Params {
	return NewParams(
		DefaultReserveTime,
		DefaultValidatorTaxRate,
		DefaultForcedSettleTime,
		DefaultPaymentAccountCountLimit,
		DefaultMaxAutoSettleFlowCount,
		DefaultMaxAutoResumeFlowCount,
		DefaultFeeDenom,
		DefaultWithdrawTimeLockThreshold,
		DefaultWithdrawTimeLockDuration,
	)
}

// ParamSetPairs get the params.ParamSet
func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
	return paramtypes.ParamSetPairs{
		paramtypes.NewParamSetPair(KeyReserveTime, &p.VersionedParams.ReserveTime, validateReserveTime),
		paramtypes.NewParamSetPair(KeyValidatorTaxRate, &p.VersionedParams.ValidatorTaxRate, validateValidatorTaxRate),
		paramtypes.NewParamSetPair(KeyForcedSettleTime, &p.ForcedSettleTime, validateForcedSettleTime),
		paramtypes.NewParamSetPair(KeyPaymentAccountCountLimit, &p.PaymentAccountCountLimit, validatePaymentAccountCountLimit),
		paramtypes.NewParamSetPair(KeyMaxAutoSettleFlowCount, &p.MaxAutoSettleFlowCount, validateMaxAutoSettleFlowCount),
		paramtypes.NewParamSetPair(KeyMaxAutoResumeFlowCount, &p.MaxAutoResumeFlowCount, validateMaxAutoResumeFlowCount),
		paramtypes.NewParamSetPair(KeyFeeDenom, &p.FeeDenom, validateFeeDenom),
		paramtypes.NewParamSetPair(KeyWithdrawTimeLockThreshold, &p.WithdrawTimeLockThreshold, validateWithdrawTimeLockThreshold),
		paramtypes.NewParamSetPair(KeyWithdrawTimeLockDuration, &p.WithdrawTimeLockDuration, validateWithdrawTimeLockDuration),
	}
}

// Validate validates the set of params
func (p Params) Validate() error {
	if err := validateReserveTime(p.VersionedParams.ReserveTime); err != nil {
		return err
	}

	if err := validateValidatorTaxRate(p.VersionedParams.ValidatorTaxRate); err != nil {
		return err
	}

	if err := validateForcedSettleTime(p.ForcedSettleTime); err != nil {
		return err
	}

	if err := validatePaymentAccountCountLimit(p.PaymentAccountCountLimit); err != nil {
		return err
	}

	if err := validateMaxAutoSettleFlowCount(p.MaxAutoSettleFlowCount); err != nil {
		return err
	}

	if err := validateMaxAutoResumeFlowCount(p.MaxAutoResumeFlowCount); err != nil {
		return err
	}

	if err := validateFeeDenom(p.FeeDenom); err != nil {
		return err
	}

	if err := validateWithdrawTimeLockThreshold(p.WithdrawTimeLockThreshold); err != nil {
		return err
	}

	if err := validateWithdrawTimeLockDuration(p.WithdrawTimeLockDuration); err != nil {
		return err
	}

	if p.VersionedParams.ReserveTime <= p.ForcedSettleTime {
		return fmt.Errorf("reserve time must be greater than force settle time")
	}

	return nil
}

// validateReserveTime validates the ReserveTime param
func validateReserveTime(v interface{}) error {
	reserveTime, ok := v.(uint64)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if reserveTime <= 0 {
		return fmt.Errorf("reserve time must be positive")
	}

	return nil
}

// validateForcedSettleTime validates the ForcedSettleTime param
func validateForcedSettleTime(v interface{}) error {
	ForcedSettleTime, ok := v.(uint64)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if ForcedSettleTime <= 0 {
		return fmt.Errorf("forced settle time must be positive")
	}
	return nil
}

// validatePaymentAccountCountLimit validates the PaymentAccountCountLimit param
func validatePaymentAccountCountLimit(v interface{}) error {
	paymentAccountCountLimit, ok := v.(uint64)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if paymentAccountCountLimit <= 0 {
		return fmt.Errorf("payment account count limit must be positive")
	}

	return nil
}

// validateMaxAutoSettleFlowCount validates the MaxAutoSettleFlowCount param
func validateMaxAutoSettleFlowCount(v interface{}) error {
	maxAutoSettleFlowCount, ok := v.(uint64)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if maxAutoSettleFlowCount <= 0 {
		return fmt.Errorf("max force settle flow count must be positive")
	}

	return nil
}

// validateMaxAutoResumeFlowCount validates the MaxAutoResumeFlowCount param
func validateMaxAutoResumeFlowCount(v interface{}) error {
	maxAutoResumeFlowCount, ok := v.(uint64)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if maxAutoResumeFlowCount <= 0 {
		return fmt.Errorf("max auto resume flow count must be positive")
	}

	return nil
}

// validateFeeDenom validates the FeeDenom param
func validateFeeDenom(v interface{}) error {
	feeDenom, ok := v.(string)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if strings.TrimSpace(feeDenom) == "" {
		return fmt.Errorf("fee denom cannt be blank")
	}

	return nil
}

// validateValidatorTaxRate validates the ValidatorTaxRate param
func validateValidatorTaxRate(v interface{}) error {
	validatorTaxRate, ok := v.(sdk.Dec)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if validatorTaxRate.IsNil() || validatorTaxRate.IsNegative() || validatorTaxRate.GT(sdk.OneDec()) {
		return fmt.Errorf("validator tax ratio should be between 0 and 1, is %s", validatorTaxRate)
	}

	return nil
}

// validateWithdrawTimeLockThreshold validates the WithdrawTimeLockThreshold param
func validateWithdrawTimeLockThreshold(v interface{}) error {
	withdrawTimeLockThreshold, ok := v.(*math.Int)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	if withdrawTimeLockThreshold != nil && !withdrawTimeLockThreshold.IsNil() {
		if !withdrawTimeLockThreshold.IsPositive() {
			return fmt.Errorf("withdraw time lock threshold should be positive, is %s", withdrawTimeLockThreshold)
		}
	}

	return nil
}

// validateWithdrawTimeLockDuration validates the WithdrawTimeLockDuration param
func validateWithdrawTimeLockDuration(v interface{}) error {
	_, ok := v.(uint64)
	if !ok {
		return fmt.Errorf("invalid parameter type: %T", v)
	}

	return nil
}
