package keeper

import (
	"context"
	"fmt"

	"cosmossdk.io/math"

	sdk "git.cw.tr/mukan-network/mukan-sdk/types"
	sdkerrors "git.cw.tr/mukan-network/mukan-sdk/types/errors"
	"git.cw.tr/mukan-network/mukan-sdk/x/protocolpool/types"
)

type MsgServer struct {
	Keeper
}

var _ types.MsgServer = MsgServer{}

// NewMsgServerImpl returns an implementation of the protocolpool MsgServer interface
// for the provided Keeper.
func NewMsgServerImpl(keeper Keeper) types.MsgServer {
	return &MsgServer{Keeper: keeper}
}

func (k MsgServer) FundCommunityPool(ctx context.Context, msg *types.MsgFundCommunityPool) (*types.MsgFundCommunityPoolResponse, error) {
	sdkCtx := sdk.UnwrapSDKContext(ctx)

	depositor, err := k.authKeeper.AddressCodec().StringToBytes(msg.Depositor)
	if err != nil {
		return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid depositor address: %s", err)
	}

	if err := validateAmount(msg.Amount); err != nil {
		return nil, err
	}

	// send funds to community pool module account
	if err := k.Keeper.FundCommunityPool(sdkCtx, msg.Amount, depositor); err != nil {
		return nil, err
	}

	return &types.MsgFundCommunityPoolResponse{}, nil
}

func (k MsgServer) CommunityPoolSpend(ctx context.Context, msg *types.MsgCommunityPoolSpend) (*types.MsgCommunityPoolSpendResponse, error) {
	sdkCtx := sdk.UnwrapSDKContext(ctx)

	if err := k.validateAuthority(msg.Authority); err != nil {
		return nil, err
	}

	if err := validateAmount(msg.Amount); err != nil {
		return nil, err
	}

	recipient, err := k.authKeeper.AddressCodec().StringToBytes(msg.Recipient)
	if err != nil {
		return nil, err
	}

	// distribute funds from community pool module account
	if err := k.DistributeFromCommunityPool(sdkCtx, msg.Amount, recipient); err != nil {
		return nil, err
	}

	sdkCtx.Logger().Debug("transferred from the community pool", "amount", msg.Amount.String(), "recipient", msg.Recipient)

	return &types.MsgCommunityPoolSpendResponse{}, nil
}

func (k MsgServer) CreateContinuousFund(ctx context.Context, msg *types.MsgCreateContinuousFund) (*types.MsgCreateContinuousFundResponse, error) {
	sdkCtx := sdk.UnwrapSDKContext(ctx)

	if err := k.validateAuthority(msg.Authority); err != nil {
		return nil, err
	}

	recipient, err := k.Keeper.authKeeper.AddressCodec().StringToBytes(msg.Recipient)
	if err != nil {
		return nil, err
	}

	// deny creation if we know this address is blocked from receiving funds
	if k.bankKeeper.BlockedAddr(recipient) {
		return nil, fmt.Errorf("recipient is blocked in the bank keeper: %s", msg.Recipient)
	}

	has, err := k.ContinuousFunds.Has(sdkCtx, recipient)
	if err != nil {
		return nil, err
	}
	if has {
		return nil, fmt.Errorf("continuous fund already exists for recipient %s", msg.Recipient)
	}

	// Validate the message fields
	err = validateContinuousFund(sdkCtx, *msg)
	if err != nil {
		return nil, err
	}

	// Check if total funds percentage exceeds 100%
	// If exceeds, we should not setup continuous fund proposal.
	totalStreamFundsPercentage := math.LegacyZeroDec()
	err = k.ContinuousFunds.Walk(sdkCtx, nil, func(key sdk.AccAddress, value types.ContinuousFund) (stop bool, err error) {
		totalStreamFundsPercentage = totalStreamFundsPercentage.Add(value.Percentage)
		return false, nil
	})
	if err != nil {
		return nil, err
	}
	totalStreamFundsPercentage = totalStreamFundsPercentage.Add(msg.Percentage)
	if totalStreamFundsPercentage.GT(math.LegacyOneDec()) {
		return nil, fmt.Errorf("cannot set continuous fund proposal\ntotal funds percentage exceeds 100\ncurrent total percentage: %s", totalStreamFundsPercentage.Sub(msg.Percentage).MulInt64(100).TruncateInt().String())
	}

	// Create continuous fund proposal
	cf := types.ContinuousFund{
		Recipient:  msg.Recipient,
		Percentage: msg.Percentage,
		Expiry:     msg.Expiry,
	}

	// Set continuous fund to the state
	err = k.ContinuousFunds.Set(sdkCtx, recipient, cf)
	if err != nil {
		return nil, err
	}

	return &types.MsgCreateContinuousFundResponse{}, nil
}

func (k MsgServer) CancelContinuousFund(ctx context.Context, msg *types.MsgCancelContinuousFund) (*types.MsgCancelContinuousFundResponse, error) {
	sdkCtx := sdk.UnwrapSDKContext(ctx)

	if err := k.validateAuthority(msg.Authority); err != nil {
		return nil, err
	}

	recipient, err := k.Keeper.authKeeper.AddressCodec().StringToBytes(msg.Recipient)
	if err != nil {
		return nil, err
	}

	canceledHeight := sdkCtx.BlockHeight()
	canceledTime := sdkCtx.BlockTime()

	has, err := k.ContinuousFunds.Has(sdkCtx, recipient)
	if err != nil {
		return nil, fmt.Errorf("cannot get continuous fund for recipient %w", err)
	}

	if !has {
		return nil, fmt.Errorf("cannot cancel continuous fund for recipient %s - does not exist", msg.Recipient)
	}

	if err := k.ContinuousFunds.Remove(sdkCtx, recipient); err != nil {
		return nil, fmt.Errorf("failed to remove continuous fund for recipient %s: %w", msg.Recipient, err)
	}

	return &types.MsgCancelContinuousFundResponse{
		CanceledTime:   canceledTime,
		CanceledHeight: uint64(canceledHeight),
		Recipient:      msg.Recipient,
	}, nil
}

func (k MsgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) {
	sdkCtx := sdk.UnwrapSDKContext(ctx)

	if err := k.validateAuthority(msg.GetAuthority()); err != nil {
		return nil, err
	}

	if err := msg.Params.Validate(); err != nil {
		return nil, fmt.Errorf("invalid params: %w", err)
	}

	if err := k.Params.Set(sdkCtx, msg.Params); err != nil {
		return nil, fmt.Errorf("failed to set params: %w", err)
	}

	return &types.MsgUpdateParamsResponse{}, nil
}
