package main

import (
	"fmt"
	"strconv"

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

	"github.com/filecoin-project/lotus/chain/actors/builtin/power"

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

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

	"github.com/ipfs/go-cid"

	"github.com/filecoin-project/lotus/chain/types"
	lcli "github.com/filecoin-project/lotus/cli"
	"github.com/urfave/cli/v2"
)

var syncCmd = &cli.Command{
	Name:  "sync",
	Usage: "tools for diagnosing sync issues",
	Flags: []cli.Flag{},
	Subcommands: []*cli.Command{
		syncValidateCmd,
		syncScrapePowerCmd,
	},
}

var syncValidateCmd = &cli.Command{
	Name:  "validate",
	Usage: "checks whether a provided tipset is valid",
	Action: func(cctx *cli.Context) error {
		api, closer, err := lcli.GetFullNodeAPI(cctx)
		if err != nil {
			return err
		}

		defer closer()
		ctx := lcli.ReqContext(cctx)

		if cctx.Args().Len() < 1 {
			fmt.Println("usage: <blockCid1> <blockCid2>...")
			fmt.Println("At least one block cid must be provided")
			return nil
		}

		args := cctx.Args().Slice()

		var tscids []cid.Cid
		for _, s := range args {
			c, err := cid.Decode(s)
			if err != nil {
				return fmt.Errorf("block cid was invalid: %s", err)
			}
			tscids = append(tscids, c)
		}

		tsk := types.NewTipSetKey(tscids...)

		valid, err := api.SyncValidateTipset(ctx, tsk)
		if err != nil {
			fmt.Println("Tipset is invalid: ", err)
		}

		if valid {
			fmt.Println("Tipset is valid")
		}

		return nil
	},
}

var syncScrapePowerCmd = &cli.Command{
	Name:      "scrape-power",
	Usage:     "given a height and a tipset, reports what percentage of mining power had a winning ticket between the tipset and height",
	ArgsUsage: "[height tipsetkey]",
	Action: func(cctx *cli.Context) error {
		if cctx.Args().Len() < 1 {
			fmt.Println("usage: <height> [blockCid1 blockCid2...]")
			fmt.Println("Any CIDs passed after the height will be used as the tipset key")
			fmt.Println("If no block CIDs are provided, chain head will be used")
			return nil
		}

		api, closer, err := lcli.GetFullNodeAPI(cctx)
		if err != nil {
			return err
		}

		defer closer()
		ctx := lcli.ReqContext(cctx)

		if cctx.Args().Len() < 1 {
			fmt.Println("usage: <blockCid1> <blockCid2>...")
			fmt.Println("At least one block cid must be provided")
			return nil
		}

		h, err := strconv.ParseInt(cctx.Args().Get(0), 10, 0)
		if err != nil {
			return err
		}

		height := abi.ChainEpoch(h)

		var ts *types.TipSet
		var startTsk types.TipSetKey
		if cctx.NArg() > 1 {
			var tscids []cid.Cid
			args := cctx.Args().Slice()

			for _, s := range args[1:] {
				c, err := cid.Decode(s)
				if err != nil {
					return fmt.Errorf("block cid was invalid: %s", err)
				}
				tscids = append(tscids, c)
			}

			startTsk = types.NewTipSetKey(tscids...)
			ts, err = api.ChainGetTipSet(ctx, startTsk)
			if err != nil {
				return err
			}
		} else {
			ts, err = api.ChainHead(ctx)
			if err != nil {
				return err
			}

			startTsk = ts.Key()
		}

		if ts.Height() < height {
			return fmt.Errorf("start tipset's height < stop height: %d < %d", ts.Height(), height)
		}

		miners := make(map[address.Address]struct{})
		for ts.Height() >= height {
			for _, blk := range ts.Blocks() {
				_, found := miners[blk.Miner]
				if !found {
					// do the thing
					miners[blk.Miner] = struct{}{}
				}
			}

			ts, err = api.ChainGetTipSet(ctx, ts.Parents())
			if err != nil {
				return err
			}
		}

		totalWonPower := power.Claim{
			RawBytePower:    big.Zero(),
			QualityAdjPower: big.Zero(),
		}
		for miner := range miners {
			mp, err := api.StateMinerPower(ctx, miner, startTsk)
			if err != nil {
				return err
			}

			totalWonPower = power.AddClaims(totalWonPower, mp.MinerPower)
		}

		totalPower, err := api.StateMinerPower(ctx, address.Undef, startTsk)
		if err != nil {
			return err
		}

		fmt.Println("Number of winning miners: ", len(miners))
		fmt.Println("QAdjPower of winning miners: ", totalWonPower.QualityAdjPower)
		fmt.Println("QAdjPower of all miners: ", totalPower.TotalPower.QualityAdjPower)
		fmt.Println("Percentage of winning QAdjPower: ", types.BigDivFloat(
			types.BigMul(totalWonPower.QualityAdjPower, big.NewInt(100)),
			totalPower.TotalPower.QualityAdjPower,
		))

		return nil
	},
}
