package keeper

import (
	"fmt"

	errorsmod "cosmossdk.io/errors"
	sdkmath "cosmossdk.io/math"
	sdk "github.com/cosmos/cosmos-sdk/types"

	"github.com/kava-labs/kava/x/swap/types"
)

// Withdraw removes liquidity from an existing pool from an owners deposit, converting the provided shares for
// the returned pool liquidity.
//
// If 100% of the owners shares are removed, then the deposit is deleted.  In addition, if all the pool shares
// are removed then the pool is deleted.
//
// The number of shares must be large enough to result in at least 1 unit of the smallest reserve in the pool.
// If the share input is below the minimum required for positive liquidity to be remove from both reserves, a
// insufficient error is returned.
//
// In addition, if the withdrawn liquidity for each reserve is below the provided minimum, a slippage exceeded
// error is returned.
func (k Keeper) Withdraw(ctx sdk.Context, owner sdk.AccAddress, shares sdkmath.Int, minCoinA, minCoinB sdk.Coin) error {
	poolID := types.PoolID(minCoinA.Denom, minCoinB.Denom)

	shareRecord, found := k.GetDepositorShares(ctx, owner, poolID)
	if !found {
		return errorsmod.Wrapf(types.ErrDepositNotFound, "no deposit for account %s and pool %s", owner, poolID)
	}

	if shares.GT(shareRecord.SharesOwned) {
		return errorsmod.Wrapf(types.ErrInvalidShares, "withdraw of %s shares greater than %s shares owned", shares, shareRecord.SharesOwned)
	}

	poolRecord, found := k.GetPool(ctx, poolID)
	if !found {
		panic(fmt.Sprintf("pool %s not found", poolID))
	}

	pool, err := types.NewDenominatedPoolWithExistingShares(poolRecord.Reserves(), poolRecord.TotalShares)
	if err != nil {
		panic(fmt.Sprintf("invalid pool %s: %s", poolID, err))
	}

	withdrawnAmount := pool.RemoveLiquidity(shares)
	if withdrawnAmount.AmountOf(minCoinA.Denom).IsZero() || withdrawnAmount.AmountOf(minCoinB.Denom).IsZero() {
		return errorsmod.Wrap(types.ErrInsufficientLiquidity, "shares must be increased")
	}
	if withdrawnAmount.AmountOf(minCoinA.Denom).LT(minCoinA.Amount) || withdrawnAmount.AmountOf(minCoinB.Denom).LT(minCoinB.Amount) {
		return errorsmod.Wrap(types.ErrSlippageExceeded, "minimum withdraw not met")
	}

	k.updatePool(ctx, poolID, pool)
	k.BeforePoolDepositModified(ctx, poolID, owner, shareRecord.SharesOwned)
	k.updateDepositorShares(ctx, owner, poolID, shareRecord.SharesOwned.Sub(shares))

	err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, owner, withdrawnAmount)
	if err != nil {
		panic(err)
	}

	ctx.EventManager().EmitEvent(
		sdk.NewEvent(
			types.EventTypeSwapWithdraw,
			sdk.NewAttribute(types.AttributeKeyPoolID, poolID),
			sdk.NewAttribute(types.AttributeKeyOwner, owner.String()),
			sdk.NewAttribute(sdk.AttributeKeyAmount, withdrawnAmount.String()),
			sdk.NewAttribute(types.AttributeKeyShares, shares.String()),
		),
	)

	return nil
}