package main

import (
	"fmt"
	"log"
	"os"
	"sort"

	"github.com/urfave/cli/v2"

	"github.com/filecoin-project/go-jsonrpc"

	"github.com/filecoin-project/lotus/api/v0api"
	lcli "github.com/filecoin-project/lotus/cli"
)

// FullAPI is a JSON-RPC client targeting a full node. It's initialized in a
// cli.BeforeFunc.
var FullAPI v0api.FullNode

// Closer is the closer for the JSON-RPC client, which must be called on
// cli.AfterFunc.
var Closer jsonrpc.ClientCloser

// DefaultLotusRepoPath is where the fallback path where to look for a Lotus
// client repo. It is expanded with mitchellh/go-homedir, so it'll work with all
// OSes despite the Unix twiddle notation.
const DefaultLotusRepoPath = "~/.lotus"

var repoFlag = cli.StringFlag{
	Name:      "repo",
	EnvVars:   []string{"LOTUS_PATH"},
	Value:     DefaultLotusRepoPath,
	TakesFile: true,
}

func main() {
	app := &cli.App{
		Name: "tvx",
		Description: `tvx is a tool for extracting and executing test vectors. It has four subcommands.

   tvx extract extracts a test vector from a live network. It requires access to
   a Filecoin client that exposes the standard JSON-RPC API endpoint. Only
   message class test vectors are supported at this time.

   tvx exec executes test vectors against Lotus. Either you can supply one in a
   file, or many as an ndjson stdin stream.

   tvx extract-many performs a batch extraction of many messages, supplied in a
   CSV file. Refer to the help of that subcommand for more info.

   tvx simulate takes a raw message and simulates it on top of the supplied
   epoch, reporting the result on stderr and writing a test vector on stdout
   or into the specified file.

   SETTING THE JSON-RPC API ENDPOINT

   You can set the JSON-RPC API endpoint through one of the following methods.

   1. Directly set the API endpoint on the FULLNODE_API_INFO env variable.
      The format is [token]:multiaddr, where token is optional for commands not
      accessing privileged operations.

   2. If you're running tvx against a local Lotus client, you can set the REPO
      env variable to have the API endpoint and token extracted from the repo.
      Alternatively, you can pass the --repo CLI flag.

   3. Rely on the default fallback, which inspects ~/.lotus and extracts the
      API endpoint string if the location is a Lotus repo.

   tvx will apply these methods in the same order of precedence they're listed.
`,
		Usage: "tvx is a tool for extracting and executing test vectors",
		Commands: []*cli.Command{
			extractCmd,
			execCmd,
			extractManyCmd,
			simulateCmd,
		},
	}

	sort.Sort(cli.CommandsByName(app.Commands))
	for _, c := range app.Commands {
		sort.Sort(cli.FlagsByName(c.Flags))
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}

func initialize(c *cli.Context) error {
	// LOTUS_DISABLE_VM_BUF disables what's called "VM state tree buffering",
	// which stashes write operations in a BufferedBlockstore
	// (https://github.com/filecoin-project/lotus/blob/b7a4dbb07fd8332b4492313a617e3458f8003b2a/lib/bufbstore/buf_bstore.go#L21)
	// such that they're not written until the VM is actually flushed.
	//
	// For some reason, the standard behaviour was not working for me (raulk),
	// and disabling it (such that the state transformations are written immediately
	// to the blockstore) worked.
	_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")

	// Make the API client.
	var err error
	if FullAPI, Closer, err = lcli.GetFullNodeAPI(c); err != nil {
		err = fmt.Errorf("failed to locate Lotus node; err: %w", err)
	}
	return err
}

func destroy(_ *cli.Context) error {
	if Closer != nil {
		Closer()
	}
	return nil
}

func ensureDir(path string) error {
	switch fi, err := os.Stat(path); {
	case os.IsNotExist(err):
		if err := os.MkdirAll(path, 0755); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", path, err)
		}
	case err == nil:
		if !fi.IsDir() {
			return fmt.Errorf("path %s is not a directory: %w", path, err)
		}
	default:
		return fmt.Errorf("failed to stat directory %s: %w", path, err)
	}
	return nil
}