// Copyright 2024 XDC Network
// XDPoS dispatcher methods ported from v2.6.8
// Fixes: https://github.com/AnilChinchawale/go-ethereum/issues/38

package XDPoS

import (
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/consensus"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/log"
)

// GetValidator returns the M2 second validator for double validation.
func (c *XDPoS) GetValidator(creator common.Address, chain consensus.ChainHeaderReader, header *types.Header) (common.Address, error) {
	masternodes := c.GetMasternodes(chain, header)
	if len(masternodes) == 0 {
		return common.Address{}, errUnauthorized
	}
	for i, mn := range masternodes {
		if mn == creator {
			validatorIndex := (i + 1) % len(masternodes)
			return masternodes[validatorIndex], nil
		}
	}
	return common.Address{}, errUnauthorized
}

// IsAuthorisedAddress checks if an address is authorised to sign blocks.
func (c *XDPoS) IsAuthorisedAddress(chain consensus.ChainHeaderReader, header *types.Header, address common.Address) bool {
	masternodes := c.GetMasternodes(chain, header)
	for _, mn := range masternodes {
		if mn == address {
			return true
		}
	}
	return false
}

// IsEpochSwitch checks if the header is an epoch switch block.
//
// For V2 blocks, this MUST delegate to EngineV2.IsEpochSwitch (round-based),
// not the block-number modulo fallback. V2 epochs advance by *round*, and with
// timeout rounds the epoch switch block is not at a fixed multiple of Epoch:
// e.g., on devnet round-1800 epoch start landed on block 4435 (not 4500). The
// V1-style fallback would mis-identify block 4500 as the switch — every caller
// that walks via this function (GetSigningTxCount, etc.) would then scan the
// wrong window and produce different totalSigner / different rewards / a
// diverging state root at epoch-switch reward blocks (e.g. block 5334).
// Canonical (XDPoSChain XDPoS.go:459) does the same delegation.
func (c *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
	number := header.Number.Uint64()
	if c.config.V2 != nil && c.config.V2.SwitchBlock != nil && number > c.config.V2.SwitchBlock.Uint64() && c.EngineV2 != nil {
		return c.EngineV2.IsEpochSwitch(header)
	}
	// V1 epoch switch (block-number modulo)
	return number%c.config.Epoch == 0, number / c.config.Epoch, nil
}

// GetCurrentEpochSwitchBlock returns the epoch switch block for the epoch containing blockNum.
func (c *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainHeaderReader, blockNum *big.Int) (uint64, uint64, error) {
	num := blockNum.Uint64()
	epochNum := num / c.config.Epoch
	switchBlock := epochNum * c.config.Epoch
	return switchBlock, epochNum, nil
}

// GetMasternodesByNumber returns masternodes at a specific block number.
func (c *XDPoS) GetMasternodesByNumber(chain consensus.ChainHeaderReader, blockNumber uint64) []common.Address {
	header := chain.GetHeaderByNumber(blockNumber)
	if header == nil {
		log.Error("[GetMasternodesByNumber] Header not found", "number", blockNumber)
		return nil
	}
	return c.GetMasternodes(chain, header)
}

// UpdateParams updates V2 consensus parameters based on round extracted from header.
// Dispatches to engine_v2.UpdateParams which updates CurrentConfig for the round.
// Matches v2.6.8 XDPoS.go UpdateParams. (#117)
func (c *XDPoS) UpdateParams(header *types.Header) {
	if c.EngineV2 != nil {
		if updater, ok := c.EngineV2.(interface{ UpdateParams(header *types.Header) }); ok {
			updater.UpdateParams(header)
			return
		}
	}
	log.Debug("[UpdateParams] EngineV2 not available or does not support UpdateParams", "block", header.Number)
}

// HandleProposedBlock handles a newly proposed block for V2 consensus.
func (c *XDPoS) HandleProposedBlock(chain consensus.ChainHeaderReader, header *types.Header) error {
	log.Debug("[HandleProposedBlock] called", "block", header.Number)
	return nil
}

// Initial initializes the consensus engine with the current chain state.
func (c *XDPoS) Initial(chain consensus.ChainHeaderReader, header *types.Header) error {
	log.Info("[Initial] Consensus engine initializing", "block", header.Number)
	return nil
}

// GetAuthorisedSignersFromSnapshot returns authorised signers from the snapshot.
func (c *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainHeaderReader, header *types.Header) ([]common.Address, error) {
	snap, err := c.GetSnapshot(chain, header)
	if err != nil {
		return nil, err
	}
	signers := make([]common.Address, 0, len(snap.Signers))
	for addr := range snap.Signers {
		signers = append(signers, addr)
	}
	return signers, nil
}
