package main

import (
	"fmt"

	"github.com/urfave/cli/v2"
	"golang.org/x/xerrors"

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

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

var cronWcCmd = &cli.Command{
	Name:        "cron-wc",
	Description: "cron stats",
	Subcommands: []*cli.Command{
		minerDeadlineCronCountCmd,
	},
}

var minerDeadlineCronCountCmd = &cli.Command{
	Name:        "deadline",
	Description: "list all addresses of miners with active deadline crons",
	Action: func(c *cli.Context) error {
		return countDeadlineCrons(c)
	},
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "tipset",
			Usage: "specify tipset state to search on (pass comma separated array of cids)",
		},
	},
}

func findDeadlineCrons(c *cli.Context) (map[address.Address]struct{}, error) {
	api, acloser, err := lcli.GetFullNodeAPI(c)
	if err != nil {
		return nil, err
	}
	defer acloser()
	ctx := lcli.ReqContext(c)

	ts, err := lcli.LoadTipSet(ctx, c, api)
	if err != nil {
		return nil, err
	}
	if ts == nil {
		ts, err = api.ChainHead(ctx)
		if err != nil {
			return nil, err
		}
	}

	mAddrs, err := api.StateListMiners(ctx, ts.Key())
	if err != nil {
		return nil, err
	}
	activeMiners := make(map[address.Address]struct{})
	for _, mAddr := range mAddrs {
		// All miners have active cron before v4.
		// v4 upgrade epoch is last epoch running v3 epoch and api.StateReadState reads
		// parent state, so v4 state isn't read until upgrade epoch + 2
		if ts.Height() <= build.UpgradeTurboHeight+1 {
			activeMiners[mAddr] = struct{}{}
			continue
		}
		st, err := api.StateReadState(ctx, mAddr, ts.Key())
		if err != nil {
			return nil, err
		}
		minerState, ok := st.State.(map[string]interface{})
		if !ok {
			return nil, xerrors.Errorf("internal error: failed to cast miner state to expected map type")
		}

		activeDlineIface, ok := minerState["DeadlineCronActive"]
		if !ok {
			return nil, xerrors.Errorf("miner %s had no deadline state, is this a v3 state root?", mAddr)
		}
		active := activeDlineIface.(bool)
		if active {
			activeMiners[mAddr] = struct{}{}
		}
	}

	return activeMiners, nil
}

func countDeadlineCrons(c *cli.Context) error {
	activeMiners, err := findDeadlineCrons(c)
	if err != nil {
		return err
	}
	for addr := range activeMiners {
		fmt.Printf("%s\n", addr)
	}

	return nil
}
