package test

import (
	"bytes"
	"context"
	"fmt"
	"math/rand"
	"sync/atomic"
	"testing"
	"time"

	logging "github.com/ipfs/go-log/v2"

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

	"github.com/filecoin-project/lotus/build"
	"github.com/filecoin-project/lotus/miner"
	"github.com/filecoin-project/lotus/node/impl"
)

//nolint:deadcode,varcheck
var log = logging.Logger("apitest")

func (ts *testSuite) testMining(t *testing.T) {
	ctx := context.Background()
	apis, sn := ts.makeNodes(t, OneFull, OneMiner)
	api := apis[0]

	newHeads, err := api.ChainNotify(ctx)
	require.NoError(t, err)
	initHead := (<-newHeads)[0]
	baseHeight := initHead.Val.Height()

	h1, err := api.ChainHead(ctx)
	require.NoError(t, err)
	require.Equal(t, int64(h1.Height()), int64(baseHeight))

	MineUntilBlock(ctx, t, apis[0], sn[0], nil)
	require.NoError(t, err)

	<-newHeads

	h2, err := api.ChainHead(ctx)
	require.NoError(t, err)
	require.Greater(t, int64(h2.Height()), int64(h1.Height()))
}

func (ts *testSuite) testMiningReal(t *testing.T) {
	build.InsecurePoStValidation = false
	defer func() {
		build.InsecurePoStValidation = true
	}()

	ctx := context.Background()
	apis, sn := ts.makeNodes(t, OneFull, OneMiner)
	api := apis[0]

	newHeads, err := api.ChainNotify(ctx)
	require.NoError(t, err)
	at := (<-newHeads)[0].Val.Height()

	h1, err := api.ChainHead(ctx)
	require.NoError(t, err)
	require.Equal(t, int64(at), int64(h1.Height()))

	MineUntilBlock(ctx, t, apis[0], sn[0], nil)
	require.NoError(t, err)

	<-newHeads

	h2, err := api.ChainHead(ctx)
	require.NoError(t, err)
	require.Greater(t, int64(h2.Height()), int64(h1.Height()))

	MineUntilBlock(ctx, t, apis[0], sn[0], nil)
	require.NoError(t, err)

	<-newHeads

	h3, err := api.ChainHead(ctx)
	require.NoError(t, err)
	require.Greater(t, int64(h3.Height()), int64(h2.Height()))
}

func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) {
	// test making a deal with a fresh miner, and see if it starts to mine

	ctx := context.Background()
	n, sn := b(t, OneFull, []StorageMiner{
		{Full: 0, Preseal: PresealGenesis},
		{Full: 0, Preseal: 0}, // TODO: Add support for miners on non-first full node
	})
	client := n[0].FullNode.(*impl.FullNodeAPI)
	provider := sn[1]
	genesisMiner := sn[0]

	addrinfo, err := client.NetAddrsListen(ctx)
	if err != nil {
		t.Fatal(err)
	}

	if err := provider.NetConnect(ctx, addrinfo); err != nil {
		t.Fatal(err)
	}

	if err := genesisMiner.NetConnect(ctx, addrinfo); err != nil {
		t.Fatal(err)
	}

	time.Sleep(time.Second)

	data := make([]byte, 600)
	rand.New(rand.NewSource(5)).Read(data)

	r := bytes.NewReader(data)
	fcid, err := client.ClientImportLocal(ctx, r)
	if err != nil {
		t.Fatal(err)
	}

	fmt.Println("FILE CID: ", fcid)

	var mine int32 = 1
	done := make(chan struct{})
	minedTwo := make(chan struct{})

	m2addr, err := sn[1].ActorAddress(context.TODO())
	if err != nil {
		t.Fatal(err)
	}

	go func() {
		defer close(done)

		complChan := minedTwo
		for atomic.LoadInt32(&mine) != 0 {
			wait := make(chan int)
			mdone := func(mined bool, _ abi.ChainEpoch, err error) {
				n := 0
				if mined {
					n = 1
				}
				wait <- n
			}

			if err := sn[0].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil {
				t.Error(err)
			}

			if err := sn[1].MineOne(ctx, miner.MineReq{Done: mdone}); err != nil {
				t.Error(err)
			}

			expect := <-wait
			expect += <-wait

			time.Sleep(blocktime)
			if expect == 0 {
				// null block
				continue
			}

			var nodeOneMined bool
			for _, node := range sn {
				mb, err := node.MiningBase(ctx)
				if err != nil {
					t.Error(err)
					return
				}

				for _, b := range mb.Blocks() {
					if b.Miner == m2addr {
						nodeOneMined = true
						break
					}
				}

			}

			if nodeOneMined && complChan != nil {
				close(complChan)
				complChan = nil
			}

		}
	}()

	deal := startDeal(t, ctx, provider, client, fcid, false, 0)

	// TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this
	time.Sleep(time.Second)

	waitDealSealed(t, ctx, provider, client, deal, false)

	<-minedTwo

	atomic.StoreInt32(&mine, 0)
	fmt.Println("shutting down mining")
	<-done
}