package main

import (
	"context"
	"fmt"
	"os"
	"sort"

	"github.com/fatih/color"
	"github.com/ipfs/go-datastore"
	cbor "github.com/ipfs/go-ipld-cbor"
	"github.com/urfave/cli/v2"
	"golang.org/x/xerrors"

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

	"github.com/filecoin-project/lotus/blockstore"
	"github.com/filecoin-project/lotus/build"
	"github.com/filecoin-project/lotus/chain/actors/adt"
	"github.com/filecoin-project/lotus/chain/actors/builtin"
	"github.com/filecoin-project/lotus/chain/actors/builtin/account"
	_init "github.com/filecoin-project/lotus/chain/actors/builtin/init"
	"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
	"github.com/filecoin-project/lotus/chain/actors/builtin/multisig"
	"github.com/filecoin-project/lotus/chain/consensus/filcns"
	"github.com/filecoin-project/lotus/chain/state"
	"github.com/filecoin-project/lotus/chain/stmgr"
	"github.com/filecoin-project/lotus/chain/store"
	"github.com/filecoin-project/lotus/chain/types"
)

type addrInfo struct {
	Key     address.Address
	Balance types.FIL
}

type msigInfo struct {
	Signers   []address.Address
	Balance   types.FIL
	Threshold uint64
}

type minerInfo struct {
}

var genesisVerifyCmd = &cli.Command{
	Name:        "verify-genesis",
	Description: "verify some basic attributes of a genesis car file",
	Action: func(cctx *cli.Context) error {
		if !cctx.Args().Present() {
			return fmt.Errorf("must pass genesis car file")
		}
		bs := blockstore.NewMemory()

		cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), filcns.Weight, nil)
		defer cs.Close() //nolint:errcheck

		cf := cctx.Args().Get(0)
		f, err := os.Open(cf)
		if err != nil {
			return xerrors.Errorf("opening the car file: %w", err)
		}

		ts, err := cs.Import(cctx.Context, f)
		if err != nil {
			return err
		}

		total, err := stmgr.CheckTotalFIL(context.TODO(), cs, ts)
		if err != nil {
			return err
		}

		fmt.Println("Genesis: ", ts.Key())
		expFIL := big.Mul(big.NewInt(int64(build.FilBase)), big.NewInt(int64(build.FilecoinPrecision)))
		fmt.Printf("Total FIL: %s", types.FIL(total))
		if !expFIL.Equals(total) {
			color.Red("  INCORRECT!")
		}
		fmt.Println()

		cst := cbor.NewCborStore(bs)

		stree, err := state.LoadStateTree(cst, ts.ParentState())
		if err != nil {
			return err
		}

		var accAddrs, msigAddrs []address.Address
		kaccounts := make(map[address.Address]addrInfo)
		kmultisigs := make(map[address.Address]msigInfo)
		kminers := make(map[address.Address]minerInfo)

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

		if err := stree.ForEach(func(addr address.Address, act *types.Actor) error {
			switch {
			case builtin.IsStorageMinerActor(act.Code):
				_, err := miner.Load(store, act)
				if err != nil {
					return xerrors.Errorf("miner actor: %w", err)
				}
				// TODO: actually verify something here?
				kminers[addr] = minerInfo{}
			case builtin.IsMultisigActor(act.Code):
				st, err := multisig.Load(store, act)
				if err != nil {
					return xerrors.Errorf("multisig actor: %w", err)
				}

				signers, err := st.Signers()
				if err != nil {
					return xerrors.Errorf("multisig actor: %w", err)
				}
				threshold, err := st.Threshold()
				if err != nil {
					return xerrors.Errorf("multisig actor: %w", err)
				}

				kmultisigs[addr] = msigInfo{
					Balance:   types.FIL(act.Balance),
					Signers:   signers,
					Threshold: threshold,
				}
				msigAddrs = append(msigAddrs, addr)
			case builtin.IsAccountActor(act.Code):
				st, err := account.Load(store, act)
				if err != nil {
					// TODO: magik6k: this _used_ to log instead of failing, why?
					return xerrors.Errorf("account actor %s: %w", addr, err)
				}
				pkaddr, err := st.PubkeyAddress()
				if err != nil {
					return xerrors.Errorf("failed to get actor pk address %s: %w", addr, err)
				}
				kaccounts[addr] = addrInfo{
					Key:     pkaddr,
					Balance: types.FIL(act.Balance.Copy()),
				}
				accAddrs = append(accAddrs, addr)
			}
			return nil
		}); err != nil {
			return err
		}

		sort.Slice(accAddrs, func(i, j int) bool {
			return accAddrs[i].String() < accAddrs[j].String()
		})

		sort.Slice(msigAddrs, func(i, j int) bool {
			return msigAddrs[i].String() < msigAddrs[j].String()
		})

		fmt.Println("Account Actors:")
		for _, acc := range accAddrs {
			a := kaccounts[acc]
			fmt.Printf("%s\t%s\t%s\n", acc, a.Key, a.Balance)
		}

		fmt.Println("Multisig Actors:")
		for _, acc := range msigAddrs {
			m := kmultisigs[acc]
			fmt.Printf("%s\t%s\t%d\t[", acc, m.Balance, m.Threshold)
			for i, s := range m.Signers {
				fmt.Print(s)
				if i != len(m.Signers)-1 {
					fmt.Print(",")
				}
			}
			fmt.Printf("]\n")
		}

		act, err := stree.GetActor(_init.Address)
		if err != nil {
			return err
		}

		ias, err := _init.Load(store, act)
		if err != nil {
			return err
		}

		nn, err := ias.NetworkName()
		if err != nil {
			return err
		}

		fmt.Println("Network name: ", nn)

		return nil
	},
}
