package test

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
	"time"

	"golang.org/x/xerrors"

	"github.com/filecoin-project/lotus/api/test"
	"github.com/filecoin-project/lotus/build"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/filecoin-project/specs-actors/v2/actors/builtin"
	"github.com/stretchr/testify/require"
	lcli "github.com/urfave/cli/v2"
)

// RunClientTest exercises some of the client CLI commands
func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode test.TestNode) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	// Create mock CLI
	mockCLI := NewMockCLI(ctx, t, cmds)
	clientCLI := mockCLI.Client(clientNode.ListenAddr)

	// Get the miner address
	addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK)
	require.NoError(t, err)
	require.Len(t, addrs, 1)

	minerAddr := addrs[0]
	fmt.Println("Miner:", minerAddr)

	// client query-ask <miner addr>
	out := clientCLI.RunCmd("client", "query-ask", minerAddr.String())
	require.Regexp(t, regexp.MustCompile("Ask:"), out)

	// Create a deal (non-interactive)
	// client deal <cid> <miner addr> 1000000attofil <duration>
	res, _, err := test.CreateClientFile(ctx, clientNode, 1)
	require.NoError(t, err)
	dataCid := res.Root
	price := "1000000attofil"
	duration := fmt.Sprintf("%d", build.MinDealDuration)
	out = clientCLI.RunCmd("client", "deal", dataCid.String(), minerAddr.String(), price, duration)
	fmt.Println("client deal", out)

	// Create a deal (interactive)
	// client deal
	// <cid>
	// <duration> (in days)
	// <miner addr>
	// "no" (verified client)
	// "yes" (confirm deal)
	res, _, err = test.CreateClientFile(ctx, clientNode, 2)
	require.NoError(t, err)
	dataCid2 := res.Root
	duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay)
	cmd := []string{"client", "deal"}
	interactiveCmds := []string{
		dataCid2.String(),
		duration,
		minerAddr.String(),
		"no",
		"yes",
	}
	out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds)
	fmt.Println("client deal:\n", out)

	// Wait for provider to start sealing deal
	dealStatus := ""
	for {
		// client list-deals
		out = clientCLI.RunCmd("client", "list-deals")
		fmt.Println("list-deals:\n", out)

		lines := strings.Split(out, "\n")
		require.Len(t, lines, 2)
		re := regexp.MustCompile(`\s+`)
		parts := re.Split(lines[1], -1)
		if len(parts) < 4 {
			require.Fail(t, "bad list-deals output format")
		}
		dealStatus = parts[3]
		fmt.Println("  Deal status:", dealStatus)
		if dealComplete(t, dealStatus) {
			break
		}

		time.Sleep(time.Second)
	}

	// Retrieve the first file from the miner
	// client retrieve <cid> <file path>
	tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-client")
	require.NoError(t, err)
	path := filepath.Join(tmpdir, "outfile.dat")
	out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path)
	fmt.Println("retrieve:\n", out)
	require.Regexp(t, regexp.MustCompile("Success"), out)
}

func dealComplete(t *testing.T, dealStatus string) bool {
	switch dealStatus {
	case "StorageDealFailing", "StorageDealError":
		t.Fatal(xerrors.Errorf("Storage deal failed with status: " + dealStatus))
	case "StorageDealStaged", "StorageDealSealing", "StorageDealActive", "StorageDealExpired", "StorageDealSlashed":
		return true
	}

	return false
}