// Copyright (c) 2013-2023 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package txscript

import (
	"testing"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/schnorr"
	"github.com/btcsuite/btcd/wire/v2"
	"github.com/stretchr/testify/require"
)

// TestDebugEngine checks that the StepCallback called during debug script
// execution contains the expected data.
func TestDebugEngine(t *testing.T) {
	t.Parallel()

	// We'll generate a private key and a signature for the tx.
	privKey, err := btcec.NewPrivateKey()
	require.NoError(t, err)

	internalKey := privKey.PubKey()

	// We use a simple script that will utilize both the stack and alt
	// stack in order to test the step callback, and wrap it in a taproot
	// witness script.
	builder := NewScriptBuilder()
	builder.AddData([]byte{0xab})
	builder.AddOp(OP_TOALTSTACK)
	builder.AddData(schnorr.SerializePubKey(internalKey))
	builder.AddOp(OP_CHECKSIG)
	builder.AddOp(OP_VERIFY)
	builder.AddOp(OP_1)
	pkScript, err := builder.Script()
	require.NoError(t, err)

	tapLeaf := NewBaseTapLeaf(pkScript)
	tapScriptTree := AssembleTaprootScriptTree(tapLeaf)

	ctrlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(
		internalKey,
	)

	tapScriptRootHash := tapScriptTree.RootNode.TapHash()
	outputKey := ComputeTaprootOutputKey(
		internalKey, tapScriptRootHash[:],
	)
	p2trScript, err := PayToTaprootScript(outputKey)
	require.NoError(t, err)

	testTx := wire.NewMsgTx(2)
	testTx.AddTxIn(&wire.TxIn{
		PreviousOutPoint: wire.OutPoint{
			Index: 1,
		},
	})
	txOut := &wire.TxOut{
		Value: 1e8, PkScript: p2trScript,
	}
	testTx.AddTxOut(txOut)

	prevFetcher := NewCannedPrevOutputFetcher(
		txOut.PkScript, txOut.Value,
	)
	sigHashes := NewTxSigHashes(testTx, prevFetcher)

	sig, err := RawTxInTapscriptSignature(
		testTx, sigHashes, 0, txOut.Value,
		txOut.PkScript, tapLeaf,
		SigHashDefault, privKey,
	)
	require.NoError(t, err)

	// Now that we have the sig, we'll make a valid witness
	// including the control block.
	ctrlBlockBytes, err := ctrlBlock.ToBytes()
	require.NoError(t, err)
	txCopy := testTx.Copy()
	txCopy.TxIn[0].Witness = wire.TxWitness{
		sig, pkScript, ctrlBlockBytes,
	}

	expCallback := []StepInfo{
		// First callback is looking at the OP_1 witness version.
		{
			ScriptIndex: 1,
			OpcodeIndex: 0,
			Stack:       [][]byte{},
			AltStack:    [][]byte{},
		},
		// The OP_1 witness version is pushed to stack,
		{
			ScriptIndex: 1,
			OpcodeIndex: 1,
			Stack:       [][]byte{{0x01}},
			AltStack:    [][]byte{},
		},
		// Then the taproot script is being executed, starting with
		// only the signature on the stacks.
		{
			ScriptIndex: 2,
			OpcodeIndex: 0,
			Stack:       [][]byte{sig},
			AltStack:    [][]byte{},
		},
		// 0xab is pushed to the stack.
		{
			ScriptIndex: 2,
			OpcodeIndex: 1,
			Stack:       [][]byte{sig, {0xab}},
			AltStack:    [][]byte{},
		},
		// 0xab is moved to the alt stack.
		{
			ScriptIndex: 2,
			OpcodeIndex: 2,
			Stack:       [][]byte{sig},
			AltStack:    [][]byte{{0xab}},
		},
		// The public key is pushed to the stack.
		{
			ScriptIndex: 2,
			OpcodeIndex: 3,
			Stack: [][]byte{
				sig,
				schnorr.SerializePubKey(internalKey),
			},
			AltStack: [][]byte{{0xab}},
		},
		// OP_CHECKSIG is executed, resulting in 0x01 on the stack.
		{
			ScriptIndex: 2,
			OpcodeIndex: 4,
			Stack: [][]byte{
				{0x01},
			},
			AltStack: [][]byte{{0xab}},
		},
		// OP_VERIFY pops and checks the top stack element.
		{
			ScriptIndex: 2,
			OpcodeIndex: 5,
			Stack:       [][]byte{},
			AltStack:    [][]byte{{0xab}},
		},
		// A single OP_1 push completes the script execution (note that
		// the alt stack is cleared when the script is "done").
		{
			ScriptIndex: 2,
			OpcodeIndex: 6,
			Stack:       [][]byte{{0x01}},
			AltStack:    [][]byte{},
		},
	}

	stepIndex := 0
	callback := func(s *StepInfo) error {
		require.Less(
			t, stepIndex, len(expCallback), "unexpected callback",
		)

		require.Equal(t, &expCallback[stepIndex], s)
		stepIndex++
		return nil
	}

	// Run the debug engine.
	vm, err := NewDebugEngine(
		txOut.PkScript, txCopy, 0, StandardVerifyFlags,
		nil, sigHashes, txOut.Value, prevFetcher,
		callback,
	)
	require.NoError(t, err)
	require.NoError(t, vm.Execute())
}
