package wallet

import (
	"math"

	"github.com/break/junhong_cmp_fiber/pkg/constants"
	appErrors "github.com/break/junhong_cmp_fiber/pkg/errors"
)

// AgentWallet 表示代理钱包聚合的资金状态。
//
// 当前聚合统一维护扣款、冻结、完成扣除、正向入账、退款回充与调额不变量。
type AgentWallet struct {
	ID            uint
	ShopID        uint
	WalletType    string
	Balance       int64
	FrozenBalance int64
	CreditEnabled bool
	CreditLimit   int64
	Status        int
	Version       int
}

// Debit 从代理主钱包扣减指定金额，并保证扣款后总可用金额不为负数。
func (w *AgentWallet) Debit(amount int64) error {
	if err := w.validateMainWalletMutation(amount); err != nil {
		return err
	}
	candidate := *w
	balance, ok := safeSub(candidate.Balance, amount)
	if !ok {
		return appErrors.New(appErrors.CodeInvalidParam, "扣减钱包余额时发生整数溢出")
	}
	candidate.Balance = balance
	if err := candidate.Validate(); err != nil {
		return err
	}
	w.Balance = balance
	return nil
}

// Credit 向代理主钱包增加账面余额，负余额会自然表现为欠款减少。
func (w *AgentWallet) Credit(amount int64) error {
	if err := w.validateMainWalletMutation(amount); err != nil {
		return err
	}
	balance, ok := safeAdd(w.Balance, amount)
	if !ok {
		return appErrors.New(appErrors.CodeInvalidParam, "增加钱包余额时发生整数溢出")
	}
	candidate := *w
	candidate.Balance = balance
	if err := candidate.Validate(); err != nil {
		return err
	}
	w.Balance = balance
	return nil
}

// Freeze 预占代理主钱包资金，冻结金额会占用现金和信用但不直接形成欠款。
func (w *AgentWallet) Freeze(amount int64) error {
	if err := w.validateMainWalletMutation(amount); err != nil {
		return err
	}
	candidate := *w
	frozen, ok := safeAdd(candidate.FrozenBalance, amount)
	if !ok {
		return appErrors.New(appErrors.CodeInvalidParam, "增加钱包冻结金额时发生整数溢出")
	}
	candidate.FrozenBalance = frozen
	if err := candidate.Validate(); err != nil {
		return err
	}
	w.FrozenBalance = frozen
	return nil
}

// Release 释放已经预占的代理主钱包资金。
func (w *AgentWallet) Release(amount int64) error {
	if err := w.validateMainWalletMutation(amount); err != nil {
		return err
	}
	if w.FrozenBalance < amount {
		return appErrors.New(appErrors.CodeInsufficientBalance, "钱包冻结金额不足")
	}
	w.FrozenBalance -= amount
	return w.Validate()
}

// CompleteReserved 完成冻结资金扣除，同时减少账面余额和冻结金额。
func (w *AgentWallet) CompleteReserved(amount int64) error {
	if err := w.validateMainWalletMutation(amount); err != nil {
		return err
	}
	if w.FrozenBalance < amount {
		return appErrors.New(appErrors.CodeInsufficientBalance, "钱包冻结金额不足")
	}
	balance, ok := safeSub(w.Balance, amount)
	if !ok {
		return appErrors.New(appErrors.CodeInvalidParam, "完成冻结资金扣除时发生整数溢出")
	}
	candidate := *w
	candidate.Balance = balance
	candidate.FrozenBalance -= amount
	if err := candidate.Validate(); err != nil {
		return err
	}
	w.Balance = candidate.Balance
	w.FrozenBalance = candidate.FrozenBalance
	return nil
}

func (w AgentWallet) validateMainWalletMutation(amount int64) error {
	if amount <= 0 {
		return appErrors.New(appErrors.CodeInvalidParam, "钱包变更金额必须大于零")
	}
	if w.WalletType != constants.AgentWalletTypeMain {
		return appErrors.New(appErrors.CodeInvalidParam, "仅代理主钱包支持该资金操作")
	}
	if w.Status != constants.AgentWalletStatusNormal {
		return appErrors.New(appErrors.CodeInvalidStatus, "当前钱包状态不允许资金操作")
	}
	return nil
}

// ChangeCredit 调整主钱包实际信用额度并重新校验完整资金边界。
func (w *AgentWallet) ChangeCredit(enabled bool, limit int64) error {
	candidate := *w
	candidate.CreditEnabled = enabled
	candidate.CreditLimit = limit
	if err := candidate.Validate(); err != nil {
		return err
	}
	w.CreditEnabled = enabled
	w.CreditLimit = limit
	return nil
}

// Validate 校验钱包类型、信用配置、版本与总可用金额不变量。
func (w AgentWallet) Validate() error {
	if w.WalletType != constants.AgentWalletTypeMain && w.WalletType != constants.AgentWalletTypeCommission {
		return appErrors.New(appErrors.CodeInvalidParam, "代理钱包类型无效")
	}
	if w.Version < 0 {
		return appErrors.New(appErrors.CodeInvalidParam, "钱包版本不能为负数")
	}
	if w.FrozenBalance < 0 {
		return appErrors.New(appErrors.CodeInvalidParam, "冻结金额不能为负数")
	}
	if err := validateCredit(w.WalletType, w.CreditEnabled, w.CreditLimit); err != nil {
		return err
	}
	if _, err := w.CashAvailableBalance(); err != nil {
		return err
	}
	available, err := w.AvailableBalance()
	if err != nil {
		return err
	}
	if available < 0 {
		return appErrors.New(appErrors.CodeInsufficientBalance, "钱包总可用金额不能为负数")
	}
	if _, err := w.DebtAmount(); err != nil {
		return err
	}
	return nil
}

// EffectiveCredit 返回当前实际生效的信用额度。
func (w AgentWallet) EffectiveCredit() int64 {
	if !w.CreditEnabled {
		return 0
	}
	return w.CreditLimit
}

// CashAvailableBalance 返回现金可用金额，即账面余额减冻结金额。
func (w AgentWallet) CashAvailableBalance() (int64, error) {
	available, ok := safeSub(w.Balance, w.FrozenBalance)
	if !ok {
		return 0, appErrors.New(appErrors.CodeInvalidParam, "计算现金可用金额时发生整数溢出")
	}
	return available, nil
}

// AvailableBalance 返回总可用金额，即现金可用金额加有效信用额度。
func (w AgentWallet) AvailableBalance() (int64, error) {
	cashAvailable, err := w.CashAvailableBalance()
	if err != nil {
		return 0, err
	}
	available, ok := safeAdd(cashAvailable, w.EffectiveCredit())
	if !ok {
		return 0, appErrors.New(appErrors.CodeInvalidParam, "计算钱包总可用金额时发生整数溢出")
	}
	return available, nil
}

// IsInDebt 返回账面余额是否已经形成欠款。
func (w AgentWallet) IsInDebt() bool {
	return w.Balance < 0
}

// DebtAmount 返回欠款金额；冻结金额不直接计入欠款。
func (w AgentWallet) DebtAmount() (int64, error) {
	if !w.IsInDebt() {
		return 0, nil
	}
	if w.Balance == math.MinInt64 {
		return 0, appErrors.New(appErrors.CodeInvalidParam, "计算钱包欠款金额时发生整数溢出")
	}
	return -w.Balance, nil
}

func validateCredit(walletType string, enabled bool, limit int64) error {
	if limit < 0 {
		return appErrors.New(appErrors.CodeInvalidParam, "信用额度不能为负数")
	}
	if !enabled && limit != 0 {
		return appErrors.New(appErrors.CodeInvalidParam, "关闭信用时信用额度必须为零")
	}
	if enabled && limit == 0 {
		return appErrors.New(appErrors.CodeInvalidParam, "启用信用时信用额度必须大于零")
	}
	if walletType != constants.AgentWalletTypeMain && (enabled || limit != 0) {
		return appErrors.New(appErrors.CodeInvalidParam, "只有代理主钱包可以启用信用额度")
	}
	return nil
}

func safeAdd(left, right int64) (int64, bool) {
	if right > 0 && left > math.MaxInt64-right {
		return 0, false
	}
	if right < 0 && left < math.MinInt64-right {
		return 0, false
	}
	return left + right, true
}

func safeSub(left, right int64) (int64, bool) {
	if right > 0 && left < math.MinInt64+right {
		return 0, false
	}
	if right < 0 && left > math.MaxInt64+right {
		return 0, false
	}
	return left - right, true
}
