// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2

package chain

import (
	"math/big"

	"dappco.re/go/core/blockchain/config"
	"dappco.re/go/core/blockchain/difficulty"
)

// nextDifficultyWith computes the expected difficulty for the block at the
// given height using the LWMA algorithm, parameterised by pre/post-HF6 targets.
//
// The genesis block (height 0) is excluded from the difficulty window,
// matching the C++ daemon's load_targetdata_cache which skips index 0.
//
// The target block time depends on the hardfork schedule:
//   - Pre-HF6: baseTarget (120s for both PoW and PoS on Lethean)
//   - Post-HF6: hf6Target (240s -- halves block rate, halves emission)
//
// NOTE: This was originally gated on HF2, matching the Zano upstream where
// HF2 coincides with the difficulty target change. Lethean mainnet keeps 120s
// blocks between HF2 (height 10,080) and HF6 (height 999,999,999), so the
// gate was corrected to HF6 in March 2026.
func (c *Chain) nextDifficultyWith(height uint64, forks []config.HardFork, baseTarget, hf6Target uint64) (uint64, error) {
	if height == 0 {
		return 1, nil
	}

	// LWMA needs N+1 entries (N solve-time intervals).
	// Start from height 1 -- genesis is excluded from the difficulty window.
	maxLookback := difficulty.LWMAWindow + 1
	lookback := min(height, maxLookback) // height excludes genesis since we start from 1

	// Start from max(1, height - lookback) to exclude genesis.
	startHeight := height - lookback
	if startHeight == 0 {
		startHeight = 1
		lookback = height - 1
	}

	if lookback == 0 {
		return 1, nil
	}

	count := int(lookback)
	timestamps := make([]uint64, count)
	cumulDiffs := make([]*big.Int, count)

	for i := range count {
		meta, err := c.getBlockMeta(startHeight + uint64(i))
		if err != nil {
			// Fewer blocks than expected -- use what we have.
			timestamps = timestamps[:i]
			cumulDiffs = cumulDiffs[:i]
			break
		}
		timestamps[i] = meta.Timestamp
		cumulDiffs[i] = new(big.Int).SetUint64(meta.CumulativeDiff)
	}

	// Determine the target block time based on hardfork status.
	// HF6 doubles the target from 120s to 240s (corrected from HF2 gate).
	target := baseTarget
	if config.IsHardForkActive(forks, config.HF6, height) {
		target = hf6Target
	}

	result := difficulty.NextDifficulty(timestamps, cumulDiffs, target)
	return result.Uint64(), nil
}

// NextDifficulty computes the expected PoW difficulty for the block at the
// given height. Pre-HF6 the target is 120s; post-HF6 it doubles to 240s.
func (c *Chain) NextDifficulty(height uint64, forks []config.HardFork) (uint64, error) {
	return c.nextDifficultyWith(height, forks, config.DifficultyPowTarget, config.DifficultyPowTargetHF6)
}

// NextPoSDifficulty computes the expected PoS difficulty for the block at the
// given height. Pre-HF6 the target is 120s; post-HF6 it doubles to 240s.
func (c *Chain) NextPoSDifficulty(height uint64, forks []config.HardFork) (uint64, error) {
	return c.nextDifficultyWith(height, forks, config.DifficultyPosTarget, config.DifficultyPosTargetHF6)
}
