package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/ipfs/go-cid"
	cbor "github.com/ipfs/go-ipld-cbor"
	"github.com/urfave/cli/v2"
	"golang.org/x/net/context"
	"golang.org/x/xerrors"

	"github.com/filecoin-project/go-address"
	"github.com/filecoin-project/go-state-types/abi"
	"github.com/filecoin-project/go-state-types/big"

	badgerbs "github.com/filecoin-project/lotus/blockstore/badger"
	"github.com/filecoin-project/lotus/chain/actors/adt"
	"github.com/filecoin-project/lotus/chain/actors/builtin"
	evm2 "github.com/filecoin-project/lotus/chain/actors/builtin/evm"
	"github.com/filecoin-project/lotus/chain/state"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/filecoin-project/lotus/node/repo"
)

var FevmAnalyticsCmd = &cli.Command{
	Name:  "evm-analytics",
	Usage: "Get FEVM related metrics",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:  "repo",
			Value: "~/.lotus",
		},
	},
	Subcommands: []*cli.Command{
		FevmBalanceCmd,
		FevmActorsCmd,
	},
}

var FevmBalanceCmd = &cli.Command{
	Name:      "evm-balance",
	Usage:     "Balances in eth accounts, evm contracts and placeholders",
	ArgsUsage: "[state root]",

	Action: func(cctx *cli.Context) error {
		if cctx.NArg() != 1 {
			return xerrors.New("only needs state root")
		}

		if !cctx.Args().Present() {
			return fmt.Errorf("must pass state root")
		}

		sroot, err := cid.Decode(cctx.Args().First())
		if err != nil {
			return fmt.Errorf("failed to parse input: %w", err)
		}

		fsrepo, err := repo.NewFS(cctx.String("repo"))
		if err != nil {
			return err
		}

		lkrepo, err := fsrepo.Lock(repo.FullNode)
		if err != nil {
			return err
		}

		defer lkrepo.Close() //nolint:errcheck

		path, err := lkrepo.SplitstorePath()
		if err != nil {
			return err
		}

		path = filepath.Join(path, "hot.badger")
		if err := os.MkdirAll(path, 0755); err != nil {
			return err
		}

		opts, err := repo.BadgerBlockstoreOptions(repo.HotBlockstore, path, lkrepo.Readonly())
		if err != nil {
			return err
		}

		bs, err := badgerbs.Open(opts)
		if err != nil {
			return err
		}

		cst := cbor.NewCborStore(bs)
		st, err := state.LoadStateTree(cst, sroot)
		if err != nil {
			return err
		}

		fmt.Println("iterating over all actors")
		count := 0
		balanceEvm := abi.NewTokenAmount(0)
		balanceEthAccount := abi.NewTokenAmount(0)
		balancePlaceholder := abi.NewTokenAmount(0)

		err = st.ForEach(func(addr address.Address, act *types.Actor) error {
			if count%200000 == 0 {
				fmt.Println("processed /n", count)
			}
			count++

			if builtin.IsEvmActor(act.Code) {
				balanceEvm = types.BigAdd(balanceEvm, act.Balance)
			}

			if builtin.IsEthAccountActor(act.Code) {
				balanceEthAccount = types.BigAdd(balanceEthAccount, act.Balance)
			}

			if builtin.IsPlaceholderActor(act.Code) {
				balancePlaceholder = types.BigAdd(balancePlaceholder, act.Balance)
			}

			return nil
		})
		if err != nil {
			return err
		}

		fmt.Println("balances in Eth contracts: ", balanceEvm)
		fmt.Println("balances in Eth accounts: ", balanceEthAccount)
		fmt.Println("balances in placeholder: ", balancePlaceholder)
		fmt.Println("Total balances: ", big.Add(big.Add(balanceEthAccount, balancePlaceholder), balanceEvm))
		return nil
	},
}

var FevmActorsCmd = &cli.Command{
	Name:      "evm-actors",
	Usage:     "actors # in eth accounts, evm contracts and placeholders",
	ArgsUsage: "[state root]",

	Action: func(cctx *cli.Context) error {
		if cctx.NArg() != 1 {
			return xerrors.New("only needs state root")
		}

		if !cctx.Args().Present() {
			return fmt.Errorf("must pass state root")
		}

		sroot, err := cid.Decode(cctx.Args().First())
		if err != nil {
			return fmt.Errorf("failed to parse input: %w", err)
		}

		fsrepo, err := repo.NewFS(cctx.String("repo"))
		if err != nil {
			return err
		}

		lkrepo, err := fsrepo.Lock(repo.FullNode)
		if err != nil {
			return err
		}

		defer lkrepo.Close() //nolint:errcheck

		path, err := lkrepo.SplitstorePath()
		if err != nil {
			return err
		}

		path = filepath.Join(path, "hot.badger")
		if err := os.MkdirAll(path, 0755); err != nil {
			return err
		}

		opts, err := repo.BadgerBlockstoreOptions(repo.HotBlockstore, path, lkrepo.Readonly())
		if err != nil {
			return err
		}

		bs, err := badgerbs.Open(opts)
		if err != nil {
			return err
		}

		ctx := context.TODO()
		cst := cbor.NewCborStore(bs)
		store := adt.WrapStore(ctx, cst)

		st, err := state.LoadStateTree(cst, sroot)
		if err != nil {
			return err
		}

		fmt.Println("iterating over all actors")
		count := 0
		EvmCount := 0
		EthAccountCount := 0
		PlaceholderCount := 0
		ea := []cid.Cid{}

		err = st.ForEach(func(addr address.Address, act *types.Actor) error {
			if count%200000 == 0 {
				fmt.Println("processed /n", count)
			}
			count++

			if builtin.IsEvmActor(act.Code) {
				EvmCount++
				e, err := evm2.Load(store, act)
				if err != nil {
					return xerrors.Errorf("fail to load evm actor: %w", err)
				}
				bcid, err := e.GetBytecodeCID()
				if err != nil {
					return err
				}

				ea = append(ea, bcid)
			}

			if builtin.IsEthAccountActor(act.Code) {
				EthAccountCount++
			}

			if builtin.IsPlaceholderActor(act.Code) {
				PlaceholderCount++
			}

			return nil
		})
		if err != nil {
			return err
		}

		uniquesa := unique(ea)
		fmt.Println("# of EVM contracts: ", EvmCount)
		fmt.Println("# of unqiue EVM contracts: ", len(uniquesa))
		fmt.Println("b# of Eth accounts: ", EthAccountCount)
		fmt.Println("# of placeholder: ", PlaceholderCount)
		return nil
	},
}

func unique(intSlice []cid.Cid) []cid.Cid {
	keys := make(map[cid.Cid]bool)
	list := []cid.Cid{}
	for _, entry := range intSlice {
		if _, value := keys[entry]; !value {
			keys[entry] = true
			list = append(list, entry)
		}
	}
	return list
}
