package prover

import (
	"crypto/ecdsa"
	"fmt"
	"math/big"
	"strings"
	"time"

	"github.com/ethereum-optimism/optimism/op-service/txmgr"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/log"
	"github.com/prysmaticlabs/prysm/v5/io/file"
	"github.com/urfave/cli/v2"

	"github.com/taikoxyz/taiko-mono/packages/taiko-client/cmd/flags"
	pkgFlags "github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/flags"
	"github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/jwt"
)

// Config contains the configurations to initialize a Taiko prover.
type Config struct {
	L1WsEndpoint                  string
	L1BeaconEndpoint              string
	L2WsEndpoint                  string
	L2EngineEndpoint              string
	JwtSecret                     string
	InboxAddress                  common.Address
	TaikoAnchorAddress            common.Address
	L1ProverPrivKey               *ecdsa.PrivateKey
	StartingProposalID            *big.Int
	BackOffMaxRetries             uint64
	BackOffRetryInterval          time.Duration
	ProveUnassignedProposals      bool
	RPCTimeout                    time.Duration
	ProveBatchesGasLimit          uint64
	RaikoHostEndpoint             string
	RaikoApiKey                   string
	RaikoRequestTimeout           time.Duration
	LocalProposerAddresses        []common.Address
	BlockConfirmations            uint64
	TxmgrConfigs                  *txmgr.CLIConfig
	PrivateTxmgrConfigs           *txmgr.CLIConfig
	ZKVMProofBufferSize           uint64
	ForceBatchProvingInterval     time.Duration
	ProofPollingInterval          time.Duration
	Dummy                         bool
	ProposalWindowSize            uint64
	MaxRisc0ProofProposalDistance uint64
	ForceSP1Proof                 bool
	ForceSGXProof                 bool
	ZkOnlyProofs                  bool
}

// NewConfigFromCliContext creates a new config instance from command line flags.
func NewConfigFromCliContext(c *cli.Context) (*Config, error) {
	var (
		raikoApiKey []byte
	)
	l1ProverPrivKey, err := crypto.ToECDSA(common.FromHex(c.String(flags.L1ProverPrivKey.Name)))
	if err != nil {
		return nil, fmt.Errorf("invalid L1 prover private key: %w", err)
	}

	// Enforce WS endpoints after the private-key validation, so existing
	// error-precedence assertions in tests stay intact.
	if err := flags.CheckWSEndpointsRequired(c, "prover"); err != nil {
		return nil, err
	}

	jwtSecret, err := jwt.ParseSecretFromFile(c.String(flags.JWTSecret.Name))
	if err != nil {
		return nil, fmt.Errorf("invalid JWT secret file: %w", err)
	}

	var startingProposalID *big.Int
	if c.IsSet(flags.StartingProposalID.Name) {
		startingProposalID = new(big.Int).SetUint64(c.Uint64(flags.StartingProposalID.Name))
	}

	if c.IsSet(flags.RaikoApiKeyPath.Name) {
		raikoApiKey, err = file.ReadFileAsBytes(c.String(flags.RaikoApiKeyPath.Name))
		if err != nil {
			return nil, fmt.Errorf("invalid ApiKey secret file: %w", err)
		}
	}

	// The Raiko endpoint is a required CLI flag. Keep explicit validation here for
	// callers that construct a CLI context without the application's flag validation.
	raikoHostEndpoint := strings.TrimSpace(c.String(flags.RaikoHostEndpoint.Name))
	if len(raikoHostEndpoint) == 0 {
		return nil, fmt.Errorf("--%s is required", flags.RaikoHostEndpoint.Name)
	}

	zkOnlyProofs := c.Bool(flags.ZkOnlyProofs.Name)

	var localProposerAddresses []common.Address
	for _, localProposerAddress := range c.StringSlice(flags.LocalProposerAddresses.Name) {
		if !common.IsHexAddress(localProposerAddress) {
			log.Debug("Invalid local proposer address", "address", localProposerAddress)
			continue
		}
		addr := common.HexToAddress(localProposerAddress)
		localProposerAddresses = append(localProposerAddresses, addr)
	}
	log.Info("Local proposer addresses", "addresses", localProposerAddresses)

	return &Config{
		L1WsEndpoint:             c.String(flags.L1WSEndpoint.Name),
		L1BeaconEndpoint:         c.String(flags.L1BeaconEndpoint.Name),
		L2WsEndpoint:             c.String(flags.L2WSEndpoint.Name),
		L2EngineEndpoint:         c.String(flags.L2AuthEndpoint.Name),
		JwtSecret:                string(jwtSecret),
		InboxAddress:             common.HexToAddress(c.String(flags.InboxAddress.Name)),
		TaikoAnchorAddress:       common.HexToAddress(c.String(flags.TaikoAnchorAddress.Name)),
		L1ProverPrivKey:          l1ProverPrivKey,
		RaikoHostEndpoint:        raikoHostEndpoint,
		RaikoApiKey:              strings.TrimSpace(string(raikoApiKey)),
		RaikoRequestTimeout:      c.Duration(flags.RaikoRequestTimeout.Name),
		StartingProposalID:       startingProposalID,
		Dummy:                    c.Bool(flags.Dummy.Name),
		BackOffMaxRetries:        c.Uint64(flags.BackOffMaxRetries.Name),
		BackOffRetryInterval:     c.Duration(flags.BackOffRetryInterval.Name),
		ProveUnassignedProposals: c.Bool(flags.ProveUnassignedProposals.Name),
		ProposalWindowSize:       c.Uint64(flags.ProposalWindowSize.Name),
		MaxRisc0ProofProposalDistance: c.Uint64(
			flags.MaxRisc0ProofProposalDistance.Name,
		),
		ForceSP1Proof:          c.Bool(flags.ForceSP1Proof.Name),
		ForceSGXProof:          c.Bool(flags.ForceSGXProof.Name),
		ZkOnlyProofs:           zkOnlyProofs,
		RPCTimeout:             c.Duration(flags.RPCTimeout.Name),
		ProveBatchesGasLimit:   c.Uint64(flags.TxGasLimit.Name),
		LocalProposerAddresses: localProposerAddresses,
		BlockConfirmations:     c.Uint64(flags.BlockConfirmations.Name),
		TxmgrConfigs:           pkgFlags.InitTxmgrConfigsFromCli(c.String(flags.L1WSEndpoint.Name), l1ProverPrivKey, c),
		PrivateTxmgrConfigs: pkgFlags.InitTxmgrConfigsFromCli(
			c.String(flags.L1PrivateEndpoint.Name),
			l1ProverPrivKey,
			c,
		),
		ZKVMProofBufferSize:       c.Uint64(flags.ZKVMBatchSize.Name),
		ForceBatchProvingInterval: c.Duration(flags.ForceBatchProvingInterval.Name),
		ProofPollingInterval:      c.Duration(flags.ProofPollingInterval.Name),
	}, nil
}
