package miner

import (
	"encoding/hex"
	"sync"

	"git.gammaspectra.live/P2Pool/go-randomx"
)

const nonceOffset = 39
const nonceSize = 4

// go-randomx is a pure-Go implementation; hardware flags are ignored internally.
const randomxFlags = 0

type Engine struct {
	mu      sync.RWMutex
	cache   *randomx.Randomx_Cache
	vm      *randomx.VM
	seedHex string
	blob    []byte
}

func NewEngine() *Engine {
	cache := randomx.Randomx_alloc_cache(randomxFlags)
	return &Engine{cache: cache}
}

func (e *Engine) SetJob(seedHex, blobHex string) error {
	seed, err := hex.DecodeString(seedHex)
	if err != nil {
		return err
	}
	blob, err := hex.DecodeString(blobHex)
	if err != nil {
		return err
	}

	e.mu.Lock()
	defer e.mu.Unlock()

	if e.seedHex != seedHex {
		e.cache.Randomx_init_cache(seed)
		// go-randomx requires SuperScalar programs to be built separately after
		// seeding the cache; Randomx_init_cache only populates the Argon2d blocks.
		// Without this step every CalculateHash call crashes with a nil-pointer.
		gen := randomx.Init_Blake2Generator(seed, 0)
		for i := range e.cache.Programs {
			e.cache.Programs[i] = randomx.Build_SuperScalar_Program(gen)
		}
		e.vm = e.cache.VM_Initialize()
		e.seedHex = seedHex
	}
	e.blob = append([]byte(nil), blob...)
	return nil
}

func (e *Engine) HashAtNonce(nonce uint32) (hashHex string, blobHex string, err error) {
	e.mu.RLock()
	defer e.mu.RUnlock()

	if e.vm == nil || len(e.blob) < nonceOffset+nonceSize {
		return "", "", nil
	}

	work := append([]byte(nil), e.blob...)
	work[nonceOffset] = byte(nonce)
	work[nonceOffset+1] = byte(nonce >> 8)
	work[nonceOffset+2] = byte(nonce >> 16)
	work[nonceOffset+3] = byte(nonce >> 24)

	out := make([]byte, 32)
	e.vm.CalculateHash(work, out)
	return hex.EncodeToString(out), hex.EncodeToString(work), nil
}
