// stm: #integration
package itests

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/filecoin-project/go-state-types/abi"

	"github.com/filecoin-project/lotus/chain/types"
	"github.com/filecoin-project/lotus/chain/wallet/key"
	"github.com/filecoin-project/lotus/itests/kit"
	"github.com/filecoin-project/lotus/markets/storageadapter"
	"github.com/filecoin-project/lotus/node"
	"github.com/filecoin-project/lotus/node/config"
	"github.com/filecoin-project/lotus/node/modules"
	"github.com/filecoin-project/lotus/storage/ctladdr"
)

var (
	publishPeriod  = 1 * time.Second
	maxDealsPerMsg = uint64(2) // Set max deals per publish deals message to 2

	blockTime = 3 * time.Millisecond
)

func TestDealsRetryLackOfFunds(t *testing.T) {
	t.Run("cover-gas", func(t *testing.T) {
		testDealsRetryLackOfFunds(t, types.NewInt(1020000000000))
	})
	t.Run("empty", func(t *testing.T) {
		testDealsRetryLackOfFunds(t, types.NewInt(1))
	})
}

func testDealsRetryLackOfFunds(t *testing.T, publishStorageAccountFunds abi.TokenAmount) {
	//stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001,
	//stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01
	//stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001
	//stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001

	//stm: @CHAIN_INCOMING_HANDLE_INCOMING_BLOCKS_001, @CHAIN_INCOMING_VALIDATE_BLOCK_PUBSUB_001, @CHAIN_INCOMING_VALIDATE_MESSAGE_PUBSUB_001
	//stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001
	ctx := context.Background()

	kit.QuietMiningLogs()

	// Allow 8MB sectors
	eightMBSectorsOpt := kit.SectorSize(8 << 20)

	publishStorageDealKey, err := key.GenerateKey(types.KTSecp256k1)
	require.NoError(t, err)

	opts := node.Options(
		node.Override(new(*storageadapter.DealPublisher),
			storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{
				Period:         publishPeriod,
				MaxDealsPerMsg: maxDealsPerMsg,
			}),
		),
		node.Override(new(*ctladdr.AddressSelector), modules.AddressSelector(&config.MinerAddressConfig{
			DealPublishControl: []string{
				publishStorageDealKey.Address.String(),
			},
			DisableOwnerFallback:  true,
			DisableWorkerFallback: true,
		})),
	)

	minerFullNode, clientFullNode, miner, ens := kit.EnsembleTwoOne(t, kit.Account(publishStorageDealKey, publishStorageAccountFunds), kit.ConstructorOpts(opts), kit.MockProofs(), eightMBSectorsOpt)

	kit.QuietMiningLogs()

	ens.
		Start().
		InterconnectAll().
		BeginMining(blockTime)

	_, err = minerFullNode.WalletImport(ctx, &publishStorageDealKey.KeyInfo)
	require.NoError(t, err)

	miner.SetControlAddresses(publishStorageDealKey.Address)

	dh := kit.NewDealHarness(t, clientFullNode, miner, miner)

	res, _ := clientFullNode.CreateImportFile(ctx, 0, 4<<20) // 4MiB file.
	list, err := clientFullNode.ClientListImports(ctx)
	require.NoError(t, err)
	require.Len(t, list, 1)
	require.Equal(t, res.Root, *list[0].Root)

	dp := dh.DefaultStartDealParams()
	dp.Data.Root = res.Root
	dp.FastRetrieval = true
	dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price.
	deal := dh.StartDeal(ctx, dp)

	propcid := *deal

	go func() {
		time.Sleep(30 * time.Second)

		kit.SendFunds(ctx, t, minerFullNode, publishStorageDealKey.Address, types.FromFil(1))

		err := miner.MarketRetryPublishDeal(ctx, propcid)
		if err != nil {
			panic(err)
		}
	}()

	dh.WaitDealSealed(ctx, deal, false, false, nil)
}

func TestDealsRetryLackOfFunds_blockInPublishDeal(t *testing.T) {
	//stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001,
	//stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_SYNC_001, @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01
	//stm: @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001
	//stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001
	//stm: @CLIENT_STORAGE_DEALS_LIST_IMPORTS_001
	ctx := context.Background()
	kit.QuietMiningLogs()

	// Allow 8MB sectors
	eightMBSectorsOpt := kit.SectorSize(8 << 20)

	publishStorageDealKey, err := key.GenerateKey(types.KTSecp256k1)
	require.NoError(t, err)

	opts := node.Options(
		node.Override(new(*storageadapter.DealPublisher),
			storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{
				Period:         publishPeriod,
				MaxDealsPerMsg: maxDealsPerMsg,
			}),
		),
		node.Override(new(*ctladdr.AddressSelector), modules.AddressSelector(&config.MinerAddressConfig{
			DealPublishControl: []string{
				publishStorageDealKey.Address.String(),
			},
			DisableOwnerFallback:  true,
			DisableWorkerFallback: true,
		})),
	)

	publishStorageAccountFunds := types.NewInt(1020000000000)
	minerFullNode, clientFullNode, miner, ens := kit.EnsembleTwoOne(t, kit.Account(publishStorageDealKey, publishStorageAccountFunds), kit.ConstructorOpts(opts), kit.MockProofs(), eightMBSectorsOpt)

	kit.QuietMiningLogs()

	ens.
		Start().
		InterconnectAll().
		BeginMining(blockTime)

	_, err = minerFullNode.WalletImport(ctx, &publishStorageDealKey.KeyInfo)
	require.NoError(t, err)

	miner.SetControlAddresses(publishStorageDealKey.Address)

	dh := kit.NewDealHarness(t, clientFullNode, miner, miner)

	res, _ := clientFullNode.CreateImportFile(ctx, 0, 4<<20) // 4MiB file.
	list, err := clientFullNode.ClientListImports(ctx)
	require.NoError(t, err)
	require.Len(t, list, 1)
	require.Equal(t, res.Root, *list[0].Root)

	dp := dh.DefaultStartDealParams()
	dp.Data.Root = res.Root
	dp.FastRetrieval = true
	dp.EpochPrice = abi.NewTokenAmount(62500000) // minimum asking price.
	deal := dh.StartDeal(ctx, dp)

	dealSealed := make(chan struct{})
	go func() {
		dh.WaitDealSealedQuiet(ctx, deal, false, false, nil)
		dealSealed <- struct{}{}
	}()

	select {
	case <-dealSealed:
		t.Fatal("deal shouldn't have sealed")
	case <-time.After(time.Second * 15):
	}
}