package modules

import (
	"bytes"
	"os"

	"github.com/ipfs/go-datastore"
	"github.com/ipld/go-car"
	"go.uber.org/fx"
	"golang.org/x/xerrors"

	"github.com/filecoin-project/lotus/chain/store"
	"github.com/filecoin-project/lotus/chain/types"
	"github.com/filecoin-project/lotus/node/modules/dtypes"
	"github.com/filecoin-project/lotus/node/modules/helpers"
)

func ErrorGenesis() Genesis {
	return func() (header *types.BlockHeader, e error) {
		return nil, xerrors.New("No genesis block provided, provide the file with 'lotus daemon --genesis=[genesis file]'")
	}
}

func LoadGenesis(genBytes []byte) func(fx.Lifecycle, helpers.MetricsCtx, dtypes.ChainBlockstore) Genesis {
	return func(lc fx.Lifecycle, mctx helpers.MetricsCtx, bs dtypes.ChainBlockstore) Genesis {
		return func() (header *types.BlockHeader, e error) {
			ctx := helpers.LifecycleCtx(mctx, lc)
			c, err := car.LoadCar(ctx, bs, bytes.NewReader(genBytes))
			if err != nil {
				return nil, xerrors.Errorf("loading genesis car file failed: %w", err)
			}
			if len(c.Roots) != 1 {
				return nil, xerrors.New("expected genesis file to have one root")
			}
			root, err := bs.Get(ctx, c.Roots[0])
			if err != nil {
				return nil, err
			}

			h, err := types.DecodeBlock(root.RawData())
			if err != nil {
				return nil, xerrors.Errorf("decoding block failed: %w", err)
			}
			return h, nil
		}
	}
}

func DoSetGenesis(_ dtypes.AfterGenesisSet) {}

func SetGenesis(lc fx.Lifecycle, mctx helpers.MetricsCtx, cs *store.ChainStore, g Genesis) (dtypes.AfterGenesisSet, error) {
	ctx := helpers.LifecycleCtx(mctx, lc)
	genFromRepo, err := cs.GetGenesis(ctx)
	if err == nil {
		if os.Getenv("LOTUS_SKIP_GENESIS_CHECK") != "_yes_" {
			expectedGenesis, err := g()
			if err != nil {
				return dtypes.AfterGenesisSet{}, xerrors.Errorf("getting expected genesis failed: %w", err)
			}

			if genFromRepo.Cid() != expectedGenesis.Cid() {
				return dtypes.AfterGenesisSet{}, xerrors.Errorf("genesis in the repo is not the one expected by this version of Lotus!")
			}
		}
		return dtypes.AfterGenesisSet{}, nil // already set, noop
	}
	if err != datastore.ErrNotFound {
		return dtypes.AfterGenesisSet{}, xerrors.Errorf("getting genesis block failed: %w", err)
	}

	genesis, err := g()
	if err != nil {
		return dtypes.AfterGenesisSet{}, xerrors.Errorf("genesis func failed: %w", err)
	}

	return dtypes.AfterGenesisSet{}, cs.SetGenesis(ctx, genesis)
}
