package main

import (
	"context"
	"fmt"

	"github.com/fatih/color"
	logging "github.com/ipfs/go-log/v2"
	"github.com/urfave/cli/v2"
	"golang.org/x/xerrors"

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

	"github.com/filecoin-project/lotus/api"
	"github.com/filecoin-project/lotus/build"
	lcli "github.com/filecoin-project/lotus/cli"
	cliutil "github.com/filecoin-project/lotus/cli/util"
	"github.com/filecoin-project/lotus/lib/lotuslog"
	"github.com/filecoin-project/lotus/lib/tracing"
	"github.com/filecoin-project/lotus/node/repo"
)

var log = logging.Logger("main")

const (
	FlagMinerRepo   = "miner-repo"
	FlagMarketsRepo = "markets-repo"
)

// TODO remove after deprecation period
const FlagMinerRepoDeprecation = "storagerepo"

func main() {
	api.RunningNodeType = api.NodeMiner

	lotuslog.SetupLogLevels()

	local := []*cli.Command{
		initCmd,
		runCmd,
		stopCmd,
		configCmd,
		backupCmd,
		lcli.WithCategory("chain", actorCmd),
		lcli.WithCategory("chain", infoCmd),
		lcli.WithCategory("market", setHidden(storageDealsCmd)),
		lcli.WithCategory("market", setHidden(retrievalDealsCmd)),
		lcli.WithCategory("market", setHidden(dataTransfersCmd)),
		lcli.WithCategory("market", setHidden(dagstoreCmd)),
		lcli.WithCategory("market", setHidden(indexProvCmd)),
		lcli.WithCategory("storage", sectorsCmd),
		lcli.WithCategory("storage", provingCmd),
		lcli.WithCategory("storage", storageCmd),
		lcli.WithCategory("storage", sealingCmd),
		lcli.WithCategory("retrieval", setHidden(piecesCmd)),
	}

	jaeger := tracing.SetupJaegerTracing("lotus")
	defer func() {
		if jaeger != nil {
			_ = jaeger.ForceFlush(context.Background())
		}
	}()

	for _, cmd := range local {
		cmd := cmd
		originBefore := cmd.Before
		cmd.Before = func(cctx *cli.Context) error {
			if jaeger != nil {
				_ = jaeger.Shutdown(cctx.Context)
			}
			jaeger = tracing.SetupJaegerTracing("lotus/" + cmd.Name)

			if cctx.IsSet("color") {
				color.NoColor = !cctx.Bool("color")
			}

			if originBefore != nil {
				return originBefore(cctx)
			}

			return nil
		}
	}

	// adapt the Net* commands to always hit the node running the markets
	// subsystem, as that is the only one that runs a libp2p node.
	netCmd := *lcli.NetCmd // make a copy.
	netCmd.Hidden = true
	prev := netCmd.Before
	netCmd.Before = func(c *cli.Context) error {
		if prev != nil {
			if err := prev(c); err != nil {
				return err
			}
		}
		c.App.Metadata["repoType"] = repo.Markets
		return nil
	}

	app := &cli.App{
		Name:                 "lotus-miner",
		Usage:                "Filecoin decentralized storage network miner",
		Version:              build.UserVersion(),
		EnableBashCompletion: true,
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:    "actor",
				Value:   "",
				Usage:   "specify other actor to query / manipulate",
				Aliases: []string{"a"},
			},
			&cli.BoolFlag{
				// examined in the Before above
				Name:        "color",
				Usage:       "use color in display output",
				DefaultText: "depends on output being a TTY",
			},
			&cli.StringFlag{
				Name:    "panic-reports",
				EnvVars: []string{"LOTUS_PANIC_REPORT_PATH"},
				Hidden:  true,
				Value:   "~/.lotusminer", // should follow --repo default
			},
			&cli.StringFlag{
				Name:    "repo",
				EnvVars: []string{"LOTUS_PATH"},
				Hidden:  true,
				Value:   "~/.lotus", // TODO: Consider XDG_DATA_HOME
			},
			&cli.StringFlag{
				Name:    FlagMinerRepo,
				Aliases: []string{FlagMinerRepoDeprecation},
				EnvVars: []string{"LOTUS_MINER_PATH", "LOTUS_STORAGE_PATH"},
				Value:   "~/.lotusminer", // TODO: Consider XDG_DATA_HOME
				Usage:   fmt.Sprintf("Specify miner repo path. flag(%s) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON", FlagMinerRepoDeprecation),
			},
			&cli.StringFlag{
				Name:    FlagMarketsRepo,
				EnvVars: []string{"LOTUS_MARKETS_PATH"},
				Hidden:  true,
			},
			&cli.BoolFlag{
				Name:   "call-on-markets",
				Usage:  "(experimental; may be removed) call this command against a markets node; use only with common commands like net, auth, pprof, etc. whose target may be ambiguous",
				Hidden: true,
			},
			cliutil.FlagVeryVerbose,
		},
		Commands: append(local, append(lcli.CommonCommands, &netCmd)...),
		Before: func(c *cli.Context) error {
			// this command is explicitly called on markets, inform
			// common commands by overriding the repoType.
			if c.Bool("call-on-markets") {
				c.App.Metadata["repoType"] = repo.Markets
			}
			return nil
		},
		After: func(c *cli.Context) error {
			if r := recover(); r != nil {
				// Generate report in LOTUS_PATH and re-raise panic
				build.GeneratePanicReport(c.String("panic-reports"), c.String(FlagMinerRepo), c.App.Name)
				panic(r)
			}
			return nil
		},
	}
	app.Setup()
	app.Metadata["repoType"] = repo.StorageMiner
	lcli.RunApp(app)
}

func getActorAddress(ctx context.Context, cctx *cli.Context) (maddr address.Address, err error) {
	if cctx.IsSet("actor") {
		maddr, err = address.NewFromString(cctx.String("actor"))
		if err != nil {
			return maddr, err
		}
		return
	}

	minerApi, closer, err := lcli.GetStorageMinerAPI(cctx)
	if err != nil {
		return address.Undef, err
	}
	defer closer()

	maddr, err = minerApi.ActorAddress(ctx)
	if err != nil {
		return maddr, xerrors.Errorf("getting actor address: %w", err)
	}

	return maddr, nil
}

func setHidden(cmd *cli.Command) *cli.Command {
	cmd.Hidden = true
	return cmd
}