// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package commands

import (
	"github.com/spf13/cobra"

	"github.com/erigontech/erigon/cmd/utils"
	"github.com/erigontech/erigon/cmd/utils/flags"
	"github.com/erigontech/erigon/common/dbg"
	"github.com/erigontech/erigon/db/state/statecfg"
	"github.com/erigontech/erigon/node/cli"
	"github.com/erigontech/erigon/node/ethconfig"
)

// The integration tool defaults parallel exec on (the integration tool's
// preferred mode for stage_exec), overriding the dbg package's default-off.
// dbg.EnvBool honours both EXEC3_PARALLEL and ERIGON_EXEC3_PARALLEL — the
// latter is what envLookup auto-prepends, so CI workflows that set
// ERIGON_EXEC3_PARALLEL=false actually take effect.
func init() {
	dbg.Exec3Parallel = dbg.EnvBool("EXEC3_PARALLEL", true)
}

var (
	chaindata                    string
	databaseVerbosity            int
	referenceChaindata           string
	block, pruneTo, unwind       uint64
	unwindEvery                  uint64
	batchSizeStr                 string
	domain                       string
	reset, squeeze, yes          bool
	bucket                       string
	datadirCli, toChaindata      string
	migration                    string
	integrityFast, integritySlow bool
	file                         string
	HeimdallURL                  string
	txtrace                      bool   // Whether to trace the execution (should only be used together with `block`)
	chain                        string // Which chain to use (mainnet, sepolia, etc.)
	outputCsvFile                string

	startTxNum uint64

	dbWriteMap bool

	chainTipMode                    bool
	clearCommitment                 bool
	resume                          bool
	noHistory                       bool
	erigondbDomainStepsInFrozenFile string
	syncCfg                         = ethconfig.Defaults.Sync

	convertSqueeze   bool
	convertNibblesV2 bool
	convertRestore   bool
	convertContinue  bool
)

func must(err error) {
	if err != nil {
		panic(err)
	}
}

func withConfig(cmd *cobra.Command) {
	cmd.Flags().String("config", "", "yaml/toml config file location")
}

func withMining(cmd *cobra.Command) {
	cmd.Flags().Bool("mine", false, "Enable mining")
	cmd.Flags().StringArray("miner.notify", nil, "Comma separated HTTP URL list to notify of new work packages")
	cmd.Flags().Uint64("miner.gaslimit", ethconfig.DefaultBlockGasLimit, "Target gas limit for mined blocks")
	cmd.Flags().String("miner.etherbase", "0", "Public address for block mining rewards (default = first account")
	cmd.Flags().String("miner.extradata", "", "Block extra data set by the miner (default = client version)")
}

func withFile(cmd *cobra.Command) {
	cmd.Flags().StringVar(&file, "file", "", "path to file")
	must(cmd.MarkFlagFilename("file"))
	must(cmd.MarkFlagRequired("file"))
}

func withReferenceChaindata(cmd *cobra.Command) {
	cmd.Flags().StringVar(&referenceChaindata, "chaindata.reference", "", "path to the 2nd (reference/etalon) db")
	must(cmd.MarkFlagDirname("chaindata.reference"))
}

func withToChaindata(cmd *cobra.Command) {
	cmd.Flags().StringVar(&toChaindata, "chaindata.to", "", "target chaindata")
	must(cmd.MarkFlagDirname("chaindata.to"))
}

func withBlock(cmd *cobra.Command) {
	cmd.Flags().Uint64Var(&block, "block", 0, "block test at this block")
}

func withUnwind(cmd *cobra.Command) {
	cmd.Flags().Uint64Var(&unwind, "unwind", 0, "how much blocks unwind on each iteration")
}
func withPruneTo(cmd *cobra.Command) {
	cmd.Flags().Uint64Var(&pruneTo, "prune.to", 0, "how much blocks unwind on each iteration")
}

func withUnwindEvery(cmd *cobra.Command) {
	cmd.Flags().Uint64Var(&unwindEvery, "unwind.every", 0, "each iteration test will move forward `--unwind.every` blocks, then unwind `--unwind` blocks")
}

func withReset(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&reset, "reset", false, "reset given stage")
}

func withYes(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&yes, "yes", false, "skip interactive prompts (for non-interactive/background use)")
}

func withSqueeze(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&squeeze, "squeeze", false, "use offset-pointers from commitment.kv to account.kv")
}

func withConvertFlags(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&convertSqueeze, "squeeze", false, "target state for the value squeeze axis: true = squeezed (offsets), false = unsqueezed (plain keys inline)")
	cmd.Flags().BoolVar(&convertNibblesV2, "nibbles.v2", false, "target state for the key encoding axis: true = V2 (prefix-sort trie locality), false = V1 (compact bytes)")
	cmd.Flags().BoolVar(&convertRestore, "restore", false, "restore commitment files from snapshots/backup/domains/ (mutually exclusive with --squeeze/--nibbles.v2)")
	cmd.Flags().BoolVar(&convertContinue, "continue", false, "Resume a prior interrupted conversion. Skips files whose converted shard already exists in <datadir>/snap/rebuild/domain/. Flags --squeeze and --nibbles.v2 MUST match the original interrupted run; mismatch produces mixed-encoding output. Mutually exclusive with --restore.")
}

func withClearCommitment(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&clearCommitment, "clear-commitment", false, "remove commitment data from DB and delete state files, then exit without rebuilding")
}

func withResume(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&resume, "resume", false, "resume a previously interrupted commitment rebuild")
}

func withNoHistory(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&noHistory, "no-history", false, "skip history regeneration and only rebuild commitment KV files")
}

func withBucket(cmd *cobra.Command) {
	cmd.Flags().StringVar(&bucket, "bucket", "", "reset given stage")
}

func withDataDir2(cmd *cobra.Command) {
	// --datadir is required, but no --chain flag: read chainConfig from db instead
	flags.DirVar(cmd.Flags(), &datadirCli, utils.DataDirFlag.Name, "", utils.DataDirFlag.Usage)
	must(cmd.MarkFlagDirname(utils.DataDirFlag.Name))
	must(cmd.MarkFlagRequired(utils.DataDirFlag.Name))

	cmd.Flags().IntVar(&databaseVerbosity, "database.verbosity", 2, "Enable internal database logs. Very high verbosity levels may require recompiling the database. The default value is 2, which means warnings are shown.")
	cmd.Flags().BoolVar(&dbWriteMap, utils.DbWriteMapFlag.Name, utils.DbWriteMapFlag.Value, utils.DbWriteMapFlag.Usage)
}

func withDataDir(cmd *cobra.Command) {
	withDataDir2(cmd)

	flags.DirVar(cmd.Flags(), &chaindata, "chaindata", "", "path to the db")
	must(cmd.MarkFlagDirname("chaindata"))
}

func withExperimentalCommitment(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&statecfg.ExperimentalParallelCommitment, utils.ExperimentalParallelCommitmentFlag.Name, statecfg.ExperimentalParallelCommitment, utils.ExperimentalParallelCommitmentFlag.Usage)
	cmd.Flags().BoolVar(&statecfg.ExperimentalStreamingCommitment, utils.ExperimentalStreamingCommitmentFlag.Name, statecfg.ExperimentalStreamingCommitment, utils.ExperimentalStreamingCommitmentFlag.Usage)
}

func withBatchSize(cmd *cobra.Command) {
	cmd.Flags().StringVar(&batchSizeStr, "batchSize", cli.BatchSizeFlag.Value, cli.BatchSizeFlag.Usage)
}

func withDomain(cmd *cobra.Command) {
	cmd.Flags().StringVar(&domain, "domain", "", "Comma separated names of domain/inverted_indices")
}

func withIntegrityChecks(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&integritySlow, "integrity.slow", false, "enable slow data-integrity checks")
	cmd.Flags().BoolVar(&integrityFast, "integrity.fast", false, "enable fast data-integrity checks")
}

func withMigration(cmd *cobra.Command) {
	cmd.Flags().StringVar(&migration, "migration", "", "action to apply to given migration")
}

func withTxTrace(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&txtrace, "txtrace", false, "enable tracing of transactions")
}

func withChain(cmd *cobra.Command) {
	cmd.Flags().StringVar(&chain, "chain", "", "pick a chain to assume (mainnet, sepolia, etc.)")
	must(cmd.MarkFlagRequired("chain"))
}

func withHeimdall(cmd *cobra.Command) {
	cmd.Flags().StringVar(&HeimdallURL, "bor.heimdall", "http://localhost:1317", "URL of Heimdall service")
}

func withWorkers(cmd *cobra.Command) {
	cmd.Flags().IntVar(&syncCfg.ExecWorkerCount, "exec.workers", ethconfig.Defaults.Sync.ExecWorkerCount, "")
}

func withStartTx(cmd *cobra.Command) {
	cmd.Flags().Uint64Var(&startTxNum, "tx", 0, "start processing from tx")
}

func withOutputCsvFile(cmd *cobra.Command) {
	cmd.Flags().StringVar(&outputCsvFile, "output.csv.file", "", "location to output csv data")
}

func withChaosMonkey(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&syncCfg.ChaosMonkey, utils.ChaosMonkeyFlag.Name, utils.ChaosMonkeyFlag.Value, utils.ChaosMonkeyFlag.Usage)
}

func withErigondbDomainStepsInFrozenFile(cmd *cobra.Command) {
	cmd.Flags().StringVar(&erigondbDomainStepsInFrozenFile,
		utils.ErigondbDomainStepsInFrozenFileFlag.Name, "",
		utils.ErigondbDomainStepsInFrozenFileFlag.Usage)
}

// withStageBase applies flags common to most stage commands: config, datadir, chain, chaos monkey, heimdall, unwind.
func withStageBase(cmd *cobra.Command) {
	withConfig(cmd)
	withDataDir(cmd)
	withChain(cmd)
	withChaosMonkey(cmd)
	withHeimdall(cmd)
	withUnwind(cmd)
}

// withTraceFlags applies flags shared by exec-style tracing commands.
func withTraceFlags(cmd *cobra.Command) {
	withBatchSize(cmd)
	withTxTrace(cmd)
	withWorkers(cmd)
}

func withChainTipMode(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&chainTipMode, "sync.mode.chaintip", false, "Every block does: `CalcCommitment`, `rwtx.Commit()`, generate diffs/changesets. Also can use it to generate diffs before `integration loop_exec`")
}
