package market

import (
	"context"
	"sync"

	"github.com/filecoin-project/specs-actors/actors/builtin"
	logging "github.com/ipfs/go-log"
	"golang.org/x/xerrors"

	"github.com/filecoin-project/go-address"
	"github.com/filecoin-project/lotus/chain/actors"
	"github.com/filecoin-project/lotus/chain/stmgr"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/filecoin-project/lotus/node/impl/full"
)

var log = logging.Logger("market_adapter")

type FundMgr struct {
	sm    *stmgr.StateManager
	mpool full.MpoolAPI

	lk        sync.Mutex
	available map[address.Address]types.BigInt
}

func NewFundMgr(sm *stmgr.StateManager, mpool full.MpoolAPI) *FundMgr {
	return &FundMgr{
		sm:    sm,
		mpool: mpool,

		available: map[address.Address]types.BigInt{},
	}
}

func (fm *FundMgr) EnsureAvailable(ctx context.Context, addr, wallet address.Address, amt types.BigInt) error {
	fm.lk.Lock()
	avail, ok := fm.available[addr]
	if !ok {
		bal, err := fm.sm.MarketBalance(ctx, addr, nil)
		if err != nil {
			fm.lk.Unlock()
			return err
		}

		avail = types.BigSub(bal.Escrow, bal.Locked)
	}

	toAdd := types.NewInt(0)
	avail = types.BigSub(avail, amt)
	if avail.LessThan(types.NewInt(0)) {
		// TODO: some rules around adding more to avoid doing stuff on-chain
		//  all the time
		toAdd = types.BigSub(toAdd, avail)
		avail = types.NewInt(0)
	}
	fm.available[addr] = avail

	fm.lk.Unlock()

	var err error
	params, err := actors.SerializeParams(&addr)
	if err != nil {
		return err
	}

	smsg, err := fm.mpool.MpoolPushMessage(ctx, &types.Message{
		To:       builtin.StorageMarketActorAddr,
		From:     wallet,
		Value:    toAdd,
		GasPrice: types.NewInt(0),
		GasLimit: 1000000,
		Method:   builtin.MethodsMarket.AddBalance,
		Params:   params,
	})
	if err != nil {
		return err
	}

	_, r, err := fm.sm.WaitForMessage(ctx, smsg.Cid())
	if err != nil {
		return xerrors.Errorf("failed waiting for market AddBalance message: %w", err)
	}

	if r.ExitCode != 0 {
		return xerrors.Errorf("adding funds to storage miner market actor failed: exit %d", r.ExitCode)
	}
	return nil
}