// Package subtreevalidation provides functionality for validating subtrees in a blockchain context.
// It handles the validation of transaction subtrees, manages transaction metadata caching,
// and interfaces with blockchain and validation services.
package subtreevalidation

import (
	"bytes"
	"context"
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"testing"

	"github.com/bsv-blockchain/go-bt/v2"
	"github.com/bsv-blockchain/go-bt/v2/bscript"
	"github.com/bsv-blockchain/go-bt/v2/chainhash"
	"github.com/bsv-blockchain/go-bt/v2/unlocker"
	"github.com/bsv-blockchain/go-chaincfg"
	bec "github.com/bsv-blockchain/go-sdk/primitives/ec"
	subtreepkg "github.com/bsv-blockchain/go-subtree"
	"github.com/bsv-blockchain/teranode/errors"
	"github.com/bsv-blockchain/teranode/pkg/fileformat"
	"github.com/bsv-blockchain/teranode/services/blockchain"
	"github.com/bsv-blockchain/teranode/services/legacy/testdata"
	"github.com/bsv-blockchain/teranode/services/validator"
	"github.com/bsv-blockchain/teranode/stores/blob"
	blobmemory "github.com/bsv-blockchain/teranode/stores/blob/memory"
	blockchainstore "github.com/bsv-blockchain/teranode/stores/blockchain"
	"github.com/bsv-blockchain/teranode/stores/utxo"
	"github.com/bsv-blockchain/teranode/stores/utxo/sql"
	"github.com/bsv-blockchain/teranode/ulogger"
	"github.com/bsv-blockchain/teranode/util/kafka" //nolint:gci
	"github.com/bsv-blockchain/teranode/util/test"
	"github.com/jarcoal/httpmock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	parentTx1, _ = bt.NewTxFromString("010000000000000000ef0158ef6d539bf88c850103fa127a92775af48dba580c36bbde4dc6d8b9da83256d050000006a47304402200ca69c5672d0e0471cd4ff1f9993f16103fc29b98f71e1a9760c828b22cae61c0220705e14aa6f3149130c3a6aa8387c51e4c80c6ae52297b2dabfd68423d717be4541210286dbe9cd647f83a4a6b29d2a2d3227a897a4904dc31769502cb013cbe5044dddffffffff8c2f6002000000001976a914308254c746057d189221c36418ba93337de33bc988ac03002d3101000000001976a91498cde576de501ceb5bb1962c6e49a4d1af17730788ac80969800000000001976a914eb7772212c334c0bdccee75c0369aa675fc21d2088ac706b9600000000001976a914a32f7eaae3afd5f73a2d6009b93f91aa11d16eef88ac00000000")
	// tx, _  = hex.DecodeString("0100000001ae73759b118b9c8d54a13ad4ccd5de662bbaa2175ee7f4b413f402affb831ed3000000006b483045022100a6af846212b0c611056a9a30c22f0eed3adc29f8c688e804509b113f322459220220708fb79c66d235d937e8348ac022b3cfa6b64fec8d35a749ea0b2293ad95da014121039f271b930111fd7c818100ee1603d5c5094c68b3d15ad0a58f712e7d766225edffffffff0550c30000000000001976a91448bea2d45f4f6175e47ccb717e4f5d19d8f68f3b88ac204e0000000000001976a91442859b9bada6461d08a0aab8a18105ef30457a8b88ac10270000000000001976a914d0e2122bdeed7b2235f670cdc832f518fb63db9f88ac0c040000000000001976a914d56f84ae869e4a743e929e31218b198f02ce67fe88ac8d0c0100000000001976a91444a8e7fb1a426e4c60597d9d3f534c677d4f858388ac00000000")
	tx1, _ = bt.NewTxFromString("010000000000000000ef0152a9231baa4e4b05dc30c8fbb7787bab5f460d4d33b039c39dd8cc006f3363e4020000006b483045022100ce3605307dd1633d3c14de4a0cf0df1439f392994e561b648897c4e540baa9ad02207af74878a7575a95c9599e9cdc7e6d73308608ee59abcd90af3ea1a5c0cca41541210275f8390df62d1e951920b623b8ef9c2a67c4d2574d408e422fb334dd1f3ee5b6ffffffff706b9600000000001976a914a32f7eaae3afd5f73a2d6009b93f91aa11d16eef88ac05404b4c00000000001976a914aabb8c2f08567e2d29e3a64f1f833eee85aaf74d88ac80841e00000000001976a914a4aff400bef2fa074169453e703c611c6b9df51588ac204e0000000000001976a9144669d92d46393c38594b2f07587f01b3e5289f6088ac204e0000000000001976a914a461497034343a91683e86b568c8945fb73aca0288ac99fe2a00000000001976a914de7850e419719258077abd37d4fcccdb0a659b9388ac00000000")
	tx2    = newTx(2)
	tx3    = newTx(3)
	tx4    = newTx(4)

	hash1 = tx1.TxIDChainHash()
	hash2 = tx2.TxIDChainHash()
	hash3 = tx3.TxIDChainHash()
	hash4 = tx4.TxIDChainHash()
)

func newTx(random uint32) *bt.Tx {
	tx := tx1.Clone()
	tx.LockTime = random

	return tx
}

func TestBlockValidationValidateSubtree(t *testing.T) {
	t.Run("validateSubtree - smoke test", func(t *testing.T) {
		InitPrometheusMetrics()

		txMetaStore, validatorClient, txStore, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		subtree, err := subtreepkg.NewTreeByLeafCount(4)
		require.NoError(t, err)
		require.NoError(t, subtree.AddNode(*hash1, 121, 0))
		require.NoError(t, subtree.AddNode(*hash2, 122, 0))
		require.NoError(t, subtree.AddNode(*hash3, 123, 0))
		require.NoError(t, subtree.AddNode(*hash4, 123, 0))

		_, err = txMetaStore.Create(context.Background(), tx1, 0)
		require.NoError(t, err)

		_, err = txMetaStore.Create(context.Background(), tx2, 0)
		require.NoError(t, err)

		_, err = txMetaStore.Create(context.Background(), tx3, 0)
		require.NoError(t, err)

		_, err = txMetaStore.Create(context.Background(), tx4, 0)
		require.NoError(t, err)

		nodeBytes, err := subtree.SerializeNodes()
		require.NoError(t, err)

		httpmock.RegisterResponder(
			"GET",
			`=~^/subtree/[a-z0-9]+\z`,
			httpmock.NewBytesResponder(200, nodeBytes),
		)

		nilConsumer := &kafka.KafkaConsumerGroup{}
		tSettings := test.CreateBaseTestSettings(t)

		subtreeValidation, err := New(context.Background(), ulogger.TestLogger{}, tSettings, subtreeStore, txStore, txMetaStore, validatorClient, blockchainClient, nilConsumer, nilConsumer)
		require.NoError(t, err)

		v := ValidateSubtree{
			SubtreeHash:   *subtree.RootHash(),
			BaseURL:       "http://localhost:8000",
			TxHashes:      nil,
			AllowFailFast: false,
		}
		_, err = subtreeValidation.ValidateSubtreeInternal(context.Background(), v, chaincfg.GenesisActivationHeight, nil)
		require.NoError(t, err)
	})
}

func setup(t *testing.T) (utxo.Store, *validator.MockValidatorClient, blob.Store, blob.Store, blockchain.ClientI, func()) {
	// we only need the httpClient, utxoStore and validatorClient when blessing a transaction
	httpmock.Activate()
	httpmock.RegisterResponder(
		"GET",
		`=~^/tx/[a-z0-9]+\z`,
		httpmock.NewBytesResponder(200, tx1.ExtendedBytes()),
	)

	httpmock.RegisterRegexpMatcherResponder(
		"POST",
		regexp.MustCompile("/subtree/[a-fA-F0-9]{64}/txs$"),
		httpmock.Matcher{},
		httpmock.NewBytesResponder(200, tx1.ExtendedBytes()),
	)

	ctx := context.Background()
	logger := ulogger.TestLogger{}

	tSettings := test.CreateBaseTestSettings(t)

	utxoStoreURL, err := url.Parse("sqlitememory:///test")
	if err != nil {
		panic(err)
	}

	utxoStore, err := sql.New(ctx, logger, tSettings, utxoStoreURL)
	if err != nil {
		panic(err)
	}

	txStore := blobmemory.New()
	subtreeStore := blobmemory.New()

	validatorClient := &validator.MockValidatorClient{UtxoStore: utxoStore}

	mockBlockChainStore := &blockchainstore.MockStore{}

	// Create LocalClient properly using the constructor to ensure all fields are initialized
	blockchainClient, err := blockchain.NewLocalClient(logger, tSettings, mockBlockChainStore, subtreeStore, utxoStore)
	if err != nil {
		panic(err)
	}

	return utxoStore, validatorClient, txStore, subtreeStore, blockchainClient, func() {
		httpmock.DeactivateAndReset()
	}
}

func TestBlockValidationValidateSubtreeInternalWithMissingTx(t *testing.T) {
	InitPrometheusMetrics()

	utxoStore, validatorClient, txStore, subtreeStore, blockchainClient, deferFunc := setup(t)
	defer deferFunc()

	subtree, err := subtreepkg.NewTreeByLeafCount(1)
	require.NoError(t, err)
	require.NoError(t, subtree.AddNode(*hash1, 121, 0))

	nodeBytes, err := subtree.SerializeNodes()
	require.NoError(t, err)

	httpmock.RegisterResponder(
		"GET",
		`=~^/subtree/[a-z0-9]+\z`,
		httpmock.NewBytesResponder(200, nodeBytes),
	)

	nilConsumer := &kafka.KafkaConsumerGroup{}

	tSettings := test.CreateBaseTestSettings(t)

	subtreeValidation, err := New(context.Background(), ulogger.TestLogger{}, tSettings, subtreeStore, txStore, utxoStore, validatorClient, blockchainClient, nilConsumer, nilConsumer)
	require.NoError(t, err)

	// Create a mock context
	ctx := context.Background()

	// Create a mock ValidateSubtree struct
	v := ValidateSubtree{
		SubtreeHash:   *hash1,
		BaseURL:       "http://localhost:8000",
		TxHashes:      nil,
		AllowFailFast: false,
	}

	// Call the ValidateSubtreeInternal method
	_, err = subtreeValidation.ValidateSubtreeInternal(ctx, v, chaincfg.GenesisActivationHeight, nil)
	require.NoError(t, err)
}

func TestBlockValidationValidateSubtreeInternalLegacy(t *testing.T) {
	InitPrometheusMetrics()

	utxoStore, validatorClient, txStore, subtreeStore, blockchainClient, deferFunc := setup(t)
	defer deferFunc()

	subtree, err := subtreepkg.NewTreeByLeafCount(2)
	require.NoError(t, err)
	require.NoError(t, subtree.AddNode(*hash1, 121, 1))
	require.NoError(t, subtree.AddNode(*hash2, 122, 2))

	txHashes := make([]chainhash.Hash, 0, 2)
	txHashes = append(txHashes, *hash1)
	txHashes = append(txHashes, *hash2)

	nodeBytes, err := subtree.SerializeNodes()
	require.NoError(t, err)

	// legacy has a subtreeToCheck and a subtreeData file stored on disk in the subtreeStore
	err = subtreeStore.Set(
		t.Context(),
		subtree.RootHash()[:],
		fileformat.FileTypeSubtreeToCheck,
		nodeBytes,
	)
	require.NoError(t, err)

	subtreeDataBytes := append(tx1.ExtendedBytes(), tx2.ExtendedBytes()...)

	err = subtreeStore.Set(
		t.Context(),
		subtree.RootHash()[:],
		fileformat.FileTypeSubtreeData,
		subtreeDataBytes,
	)
	require.NoError(t, err)

	nilConsumer := &kafka.KafkaConsumerGroup{}

	tSettings := test.CreateBaseTestSettings(t)

	subtreeValidation, err := New(context.Background(), ulogger.TestLogger{}, tSettings, subtreeStore, txStore, utxoStore, validatorClient, blockchainClient, nilConsumer, nilConsumer)
	require.NoError(t, err)

	// Create a mock context
	ctx := context.Background()

	// Create a mock ValidateSubtree struct
	v := ValidateSubtree{
		SubtreeHash:   *subtree.RootHash(),
		BaseURL:       "legacy",
		TxHashes:      txHashes,
		AllowFailFast: false,
	}

	// Call the ValidateSubtreeInternal method
	_, err = subtreeValidation.ValidateSubtreeInternal(ctx, v, chaincfg.GenesisActivationHeight, nil)
	require.NoError(t, err)
}

func TestServer_prepareTxsPerLevel(t *testing.T) {
	t.Run("per block", func(t *testing.T) {
		testCases := []struct {
			name             string
			blockFilePath    string
			expectedLevels   uint32
			expectedTxMapLen int
		}{
			{
				name:             "Block1",
				blockFilePath:    "../legacy/testdata/00000000000000000ad4cd15bbeaf6cb4583c93e13e311f9774194aadea87386.bin",
				expectedLevels:   15,
				expectedTxMapLen: 563,
			},
		}

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				block, err := testdata.ReadBlockFromFile(tc.blockFilePath)
				require.NoError(t, err)

				s := &Server{}

				transactions := make([]missingTx, 0)

				for _, wireTx := range block.Transactions() {
					// Serialize the tx
					var txBytes bytes.Buffer
					err = wireTx.MsgTx().Serialize(&txBytes)
					require.NoError(t, err)

					tx, err := bt.NewTxFromBytes(txBytes.Bytes())
					require.NoError(t, err)

					if tx.IsCoinbase() {
						continue
					}

					transactions = append(transactions, missingTx{
						tx: tx,
					})
				}

				maxLevel, blockTXsPerLevel, err := s.prepareTxsPerLevel(context.Background(), transactions)
				require.NoError(t, err)
				assert.Equal(t, tc.expectedLevels, maxLevel)

				allParents := 0

				for i := range blockTXsPerLevel {
					allParents += len(blockTXsPerLevel[i])
				}

				assert.Equal(t, tc.expectedTxMapLen, allParents)
			})
		}
	})

	t.Run("from subtree", func(t *testing.T) {
		s := &Server{}

		subtreeBytes, err := os.ReadFile("testdata/4d22d3ea8d618c6de784855bf4facd0760f4012852242adfd399cff700665f3d.subtree")
		require.NoError(t, err)

		subtree, err := subtreepkg.NewSubtreeFromBytes(subtreeBytes[8:]) // trim the magic bytes
		require.NoError(t, err)

		subtreeDataBytes, err := os.ReadFile("testdata/4d22d3ea8d618c6de784855bf4facd0760f4012852242adfd399cff700665f3d.subtreeData")
		require.NoError(t, err)

		subtreeData, err := subtreepkg.NewSubtreeDataFromBytes(subtree, subtreeDataBytes)
		require.NoError(t, err)

		transactions := make([]missingTx, 0, len(subtreeData.Txs))
		for idx, tx := range subtreeData.Txs {
			if tx == nil {
				continue
			}

			transactions = append(transactions, missingTx{
				tx:  tx,
				idx: idx,
			})
		}

		maxLevel, blockTXsPerLevel, err := s.prepareTxsPerLevel(context.Background(), transactions)
		require.NoError(t, err)
		assert.Equal(t, uint32(330), maxLevel)

		allParents := 0

		for i := range blockTXsPerLevel {
			allParents += len(blockTXsPerLevel[i])
		}

		assert.Equal(t, len(subtreeData.Txs)-1, allParents) // ignore coinbase placeholder tx

		// parenTxHash, err := chainhash.NewHashFromStr("061245c112df5b66778698c4d96f2e1a82051094f084207c38cd4b0076e6ab3c")
		// require.NoError(t, err)
		//
		// childTxHash, err := chainhash.NewHashFromStr("c3595a5877702baa8fbddbf38ae67d43a4f06105d5a9bbf26ef23eab98784d1d")
		// require.NoError(t, err)

		levelsMap := make(map[chainhash.Hash]int)

		// make sure the child is found in a higher level than the parent
		for level, txs := range blockTXsPerLevel {
			for _, tx := range txs {
				levelsMap[*tx.tx.TxIDChainHash()] = level
			}
		}

		// check all transactions that the parents of the transaction are either not in the map, or at a lower level
		for _, tx := range transactions {
			if tx.tx == nil {
				continue
			}

			txLevel := levelsMap[*tx.tx.TxIDChainHash()]
			for _, input := range tx.tx.Inputs {
				parentHash := *input.PreviousTxIDChainHash()

				if level, ok := levelsMap[parentHash]; ok {
					assert.Less(t, level, txLevel, "Parent transaction should be at a lower level than the child transaction")
				}
			}
		}
	})
}

func Benchmark_prepareTxsPerLevel(b *testing.B) {
	s := &Server{}

	subtreeBytes, err := os.ReadFile("testdata/4d22d3ea8d618c6de784855bf4facd0760f4012852242adfd399cff700665f3d.subtree")
	require.NoError(b, err)

	subtree, err := subtreepkg.NewSubtreeFromBytes(subtreeBytes[8:]) // trim the magic bytes
	require.NoError(b, err)

	subtreeDataBytes, err := os.ReadFile("testdata/4d22d3ea8d618c6de784855bf4facd0760f4012852242adfd399cff700665f3d.subtreeData")
	require.NoError(b, err)

	subtreeData, err := subtreepkg.NewSubtreeDataFromBytes(subtree, subtreeDataBytes)
	require.NoError(b, err)

	transactions := make([]missingTx, 0, len(subtreeData.Txs))
	for idx, tx := range subtreeData.Txs {
		if tx == nil {
			continue
		}

		tx.SetTxHash(tx.TxIDChainHash()) // ensure the tx hash is set, as the tx id is not mutated in the benchmark
		transactions = append(transactions, missingTx{
			tx:  tx,
			idx: idx,
		})
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, _, _ = s.prepareTxsPerLevel(context.Background(), transactions)
	}
}

func createSpendingTx(t *testing.T, prevTx *bt.Tx, vout uint32, amount uint64, address *bscript.Address, privateKey *bec.PrivateKey) *bt.Tx {
	tx := bt.NewTx()
	err := tx.FromUTXOs(&bt.UTXO{
		TxIDHash:      prevTx.TxIDChainHash(),
		Vout:          vout,
		LockingScript: prevTx.Outputs[vout].LockingScript,
		Satoshis:      prevTx.Outputs[vout].Satoshis,
	})
	require.NoError(t, err)

	err = tx.AddP2PKHOutputFromAddress(address.AddressString, amount)
	require.NoError(t, err)

	err = tx.FillAllInputs(context.Background(), &unlocker.Getter{PrivateKey: privateKey})
	require.NoError(t, err)

	return tx
}

func createTestTransactionChainWithCount(t *testing.T, count int) []*bt.Tx {
	privateKey, err := bec.NewPrivateKey()
	require.NoError(t, err)

	address, err := bscript.NewAddressFromPublicKey(privateKey.PubKey(), true)
	require.NoError(t, err)

	// Create coinbase transaction
	coinbaseTx := bt.NewTx()
	err = coinbaseTx.From(
		"0000000000000000000000000000000000000000000000000000000000000000",
		0xffffffff,
		"",
		0,
	)
	require.NoError(t, err)

	blockHeight := uint32(100)
	blockHeightBytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(blockHeightBytes, blockHeight)

	arbitraryData := []byte{}
	arbitraryData = append(arbitraryData, 0x03)
	arbitraryData = append(arbitraryData, blockHeightBytes[:3]...)
	arbitraryData = append(arbitraryData, []byte("/Test miner/")...)
	coinbaseTx.Inputs[0].UnlockingScript = bscript.NewFromBytes(arbitraryData)

	err = coinbaseTx.AddP2PKHOutputFromAddress(address.AddressString, 50e8)
	require.NoError(t, err)

	// Create tx1 with multiple outputs for the chain
	tx1 := bt.NewTx()
	err = tx1.FromUTXOs(&bt.UTXO{
		TxIDHash:      coinbaseTx.TxIDChainHash(),
		Vout:          0,
		LockingScript: coinbaseTx.Outputs[0].LockingScript,
		Satoshis:      coinbaseTx.Outputs[0].Satoshis,
	})
	require.NoError(t, err)

	// Create enough outputs for the chain
	outputAmount := coinbaseTx.Outputs[0].Satoshis / uint64(count) // nolint: gosec

	for i := 0; i < count; i++ {
		if i == 0 {
			// First output leaves some fees
			err = tx1.AddP2PKHOutputFromAddress(address.AddressString, outputAmount-1000)
			require.NoError(t, err)
		} else {
			err = tx1.AddP2PKHOutputFromAddress(address.AddressString, outputAmount)
			require.NoError(t, err)
		}
	}

	err = tx1.FillAllInputs(context.Background(), &unlocker.Getter{PrivateKey: privateKey})
	require.NoError(t, err)

	// Create the chain of transactions
	result := []*bt.Tx{coinbaseTx, tx1}

	for i := 0; i < count; i++ {
		//nolint:gosec
		tx := createSpendingTx(t, tx1, uint32(i), tx1.Outputs[i].Satoshis-1000, address, privateKey)
		result = append(result, tx)
	}

	return result
}

// TNE-1.1: If Teranode has already validated the same transaction it is not required to perform
// the same validation again. The transaction can already be considered valid.
func TestSubtreeValidationWhenBlessMissingTransactions(t *testing.T) {
	t.Run("test get subtree tx hashes", func(t *testing.T) {
		InitPrometheusMetrics()
		// Setup test
		utxoStore, validatorClient, txStore, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create test transactions
		txs := createTestTransactionChainWithCount(t, 7)

		coinbaseTx, tx1, tx2, tx3, tx4, tx5, tx6 := txs[0], txs[1], txs[2], txs[3], txs[4], txs[5], txs[6]

		// Store initial transactions in txMetaStore
		_, err := utxoStore.Create(context.Background(), coinbaseTx, 1)
		require.NoError(t, err)

		_, err = utxoStore.Create(context.Background(), tx1, 1)
		require.NoError(t, err)

		_, err = utxoStore.Create(context.Background(), tx2, 1)
		require.NoError(t, err)

		_, err = utxoStore.Create(context.Background(), tx3, 1)
		require.NoError(t, err)

		_, err = utxoStore.Create(context.Background(), tx4, 1)
		require.NoError(t, err)

		// Create subtrees
		subtree1, err := subtreepkg.NewTreeByLeafCount(4)
		require.NoError(t, err)
		subtree2, err := subtreepkg.NewTreeByLeafCount(4)
		require.NoError(t, err)

		// Add transactions to subtrees
		hash1 := tx1.TxIDChainHash()
		hash2 := tx2.TxIDChainHash()
		hash3 := tx3.TxIDChainHash()
		hash4 := tx4.TxIDChainHash()
		hash5 := tx5.TxIDChainHash()
		hash6 := tx6.TxIDChainHash()

		// Create subtree1 with tx1, tx2, tx3, tx4
		require.NoError(t, subtree1.AddNode(*hash1, 121, 0))
		require.NoError(t, subtree1.AddNode(*hash2, 122, 0))
		require.NoError(t, subtree1.AddNode(*hash3, 123, 0))
		require.NoError(t, subtree1.AddNode(*hash4, 124, 0))

		// Create subtree2 with tx1, tx2, tx5, tx6
		require.NoError(t, subtree2.AddNode(*hash1, 121, 0))
		require.NoError(t, subtree2.AddNode(*hash2, 122, 0))
		require.NoError(t, subtree2.AddNode(*hash5, 123, 0))
		require.NoError(t, subtree2.AddNode(*hash6, 124, 0))

		// Setup HTTP mocks
		nodes1Bytes, err := subtree1.SerializeNodes()
		require.NoError(t, err)
		nodes2Bytes, err := subtree2.SerializeNodes()
		require.NoError(t, err)

		httpmock.RegisterResponder(
			"GET",
			fmt.Sprintf("/subtree/%s", subtree1.RootHash().String()),
			httpmock.NewBytesResponder(200, nodes1Bytes),
		)
		httpmock.RegisterResponder(
			"GET",
			fmt.Sprintf("/subtree/%s", subtree2.RootHash().String()),
			httpmock.NewBytesResponder(200, nodes2Bytes),
		)

		httpmock.RegisterRegexpMatcherResponder(
			"POST",
			regexp.MustCompile("/subtree/[a-fA-F0-9]{64}/txs$"),
			httpmock.Matcher{},
			func(req *http.Request) (*http.Response, error) {
				body, err := io.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}

				numHashes := len(body) / 32

				var responseBytes []byte

				for i := 0; i < numHashes; i++ {
					hashBytes := body[i*32 : (i+1)*32]

					hash, err := chainhash.NewHash(hashBytes)
					require.NoError(t, err)

					var tx *bt.Tx

					switch hash.String() {
					case tx5.TxIDChainHash().String():
						tx = tx5
					case tx6.TxIDChainHash().String():
						tx = tx6
					default:
						return nil, errors.NewError("unexpected hash in request: " + hash.String())
					}

					responseBytes = append(responseBytes, tx.ExtendedBytes()...)
				}

				return httpmock.NewBytesResponse(200, responseBytes), nil
			},
		)

		// Setup and run validation
		nilConsumer := &kafka.KafkaConsumerGroup{}
		tSettings := test.CreateBaseTestSettings(t)
		subtreeValidation, err := New(context.Background(), ulogger.TestLogger{}, tSettings, subtreeStore, txStore, utxoStore, validatorClient, blockchainClient, nilConsumer, nilConsumer)
		require.NoError(t, err)

		// Validate subtree1
		v1 := ValidateSubtree{
			SubtreeHash:   *subtree1.RootHash(),
			BaseURL:       "http://localhost:8000",
			TxHashes:      nil,
			AllowFailFast: false,
		}
		_, err = subtreeValidation.ValidateSubtreeInternal(context.Background(), v1, 100, nil)
		require.NoError(t, err)

		// Verify initial cache state
		_, err = utxoStore.Get(context.Background(), hash1)
		require.NoError(t, err, "tx1 should be in cache")
		_, err = utxoStore.Get(context.Background(), hash2)
		require.NoError(t, err, "tx2 should be in cache")
		_, err = utxoStore.Get(context.Background(), hash3)
		require.NoError(t, err, "tx3 should be in cache")
		_, err = utxoStore.Get(context.Background(), hash4)
		require.NoError(t, err, "tx4 should be in cache")
		_, err = utxoStore.Get(context.Background(), hash5)
		require.Error(t, err, "tx5 should NOT be in cache before processing subtree2")
		_, err = utxoStore.Get(context.Background(), hash6)
		require.Error(t, err, "tx6 should NOT be in cache before processing subtree2")

		// Validate subtree2
		v2 := ValidateSubtree{
			SubtreeHash:   *subtree2.RootHash(),
			BaseURL:       "http://localhost:8000",
			TxHashes:      nil,
			AllowFailFast: false,
		}
		_, err = subtreeValidation.ValidateSubtreeInternal(context.Background(), v2, 100, nil)
		require.NoError(t, err)

		// Verify final cache state
		_, err = utxoStore.Get(context.Background(), hash1)
		require.NoError(t, err, "tx1 should still be in cache")
		_, err = utxoStore.Get(context.Background(), hash2)
		require.NoError(t, err, "tx2 should still be in cache")
		_, err = utxoStore.Get(context.Background(), hash3)
		require.NoError(t, err, "tx3 should still be in cache")
		_, err = utxoStore.Get(context.Background(), hash4)
		require.NoError(t, err, "tx4 should still be in cache")
		_, err = utxoStore.Get(context.Background(), hash5)
		require.NoError(t, err, "tx5 should now be in cache")
		_, err = utxoStore.Get(context.Background(), hash6)
		require.NoError(t, err, "tx6 should now be in cache")
	})
}

func Test_checkCounterConflictingOnCurrentChain(t *testing.T) {
	InitPrometheusMetrics()

	t.Run("checkCounterConflictingOnCurrentChain - smoke test", func(t *testing.T) {
		ctx := context.Background()
		logger := ulogger.NewErrorTestLogger(t)

		tSettings := test.CreateBaseTestSettings(t)

		utxoStoreURL, err := url.Parse("sqlitememory:///test")
		require.NoError(t, err)

		utxoStore, err := sql.New(ctx, logger, tSettings, utxoStoreURL)
		require.NoError(t, err)

		// Create a mock Server struct
		s := &Server{
			utxoStore: utxoStore,
		}

		_, err = s.utxoStore.Create(ctx, parentTx1, 123)
		require.NoError(t, err)

		_, err = s.utxoStore.Create(ctx, tx1, 123)
		require.NoError(t, err)

		// Call the checkCounterConflictingOnCurrentChain method
		err = s.checkCounterConflictingOnCurrentChain(ctx, *tx1.TxIDChainHash(), map[uint32]bool{})
		require.NoError(t, err)
	})

	t.Run("checkCounterConflictingOnCurrentChain - counter conflicting mined", func(t *testing.T) {
		ctx := context.Background()
		logger := ulogger.NewErrorTestLogger(t)

		tSettings := test.CreateBaseTestSettings(t)

		utxoStoreURL, err := url.Parse("sqlitememory:///test")
		require.NoError(t, err)

		utxoStore, err := sql.New(ctx, logger, tSettings, utxoStoreURL)
		require.NoError(t, err)

		// Create a mock Server struct
		s := &Server{
			utxoStore: utxoStore,
		}

		_, err = s.utxoStore.Create(ctx, parentTx1, 122)
		require.NoError(t, err)

		tx1DoubleSpend := tx1.Clone()
		tx1DoubleSpend.Version = 2

		// spend the parent tx with tx2
		_, err = s.utxoStore.Spend(ctx, tx1DoubleSpend)
		require.NoError(t, err)

		_, err = s.utxoStore.Create(ctx, tx1DoubleSpend, 122)
		require.NoError(t, err)

		_, err = s.utxoStore.Create(ctx, tx1, 123, utxo.WithConflicting(true))
		require.NoError(t, err)

		// Call the checkCounterConflictingOnCurrentChain method, should be OK since tx1DoubleSpend has not been mined
		err = s.checkCounterConflictingOnCurrentChain(ctx, *tx1.TxIDChainHash(), map[uint32]bool{})
		require.NoError(t, err)

		// set the tx1DoubleSpend to mined
		blockIDsMap, err := s.utxoStore.SetMinedMulti(ctx, []*chainhash.Hash{tx1DoubleSpend.TxIDChainHash()}, utxo.MinedBlockInfo{
			BlockID:     122,
			BlockHeight: 122,
			SubtreeIdx:  0,
		})
		require.NoError(t, err)
		require.Len(t, blockIDsMap, 1)
		require.Equal(t, []uint32{122}, blockIDsMap[*tx1DoubleSpend.TxIDChainHash()])

		// Call the checkCounterConflictingOnCurrentChain method, should be OK since tx1DoubleSpend has not been mined
		err = s.checkCounterConflictingOnCurrentChain(ctx, *tx1.TxIDChainHash(), map[uint32]bool{
			122: true,
		})
		require.Error(t, err, "should be an error since tx1DoubleSpend has been mined")
	})
}

func Test_getSubtreeMissingTxs(t *testing.T) {
	tSettings := test.CreateBaseTestSettings(t)

	subtree, err := subtreepkg.NewTreeByLeafCount(4)
	require.NoError(t, err)
	require.NoError(t, subtree.AddNode(*hash1, 121, 0))
	require.NoError(t, subtree.AddNode(*hash2, 122, 0))
	require.NoError(t, subtree.AddNode(*hash3, 123, 0))
	require.NoError(t, subtree.AddNode(*hash4, 123, 0))

	t.Run("getSubtreeMissingTxs - smoke test", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		missingTxs, err := s.getSubtreeMissingTxs(t.Context(), *subtree.RootHash(), subtree, []utxo.UnresolvedMetaData{}, []chainhash.Hash{}, "test")
		require.NoError(t, err, "should be no error since all txs are in the subtree")

		require.Len(t, missingTxs, 0, "should be no missing txs since all txs are in the subtree")
	})

	t.Run("getSubtreeMissingTxs - from peer", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash1,
		}, {
			Hash: *hash2,
		}}

		httpmock.RegisterRegexpMatcherResponder(
			"POST",
			regexp.MustCompile("/subtree/[a-fA-F0-9]{64}/txs$"),
			httpmock.Matcher{},
			httpmock.NewBytesResponder(200, append(tx1.ExtendedBytes(), tx2.ExtendedBytes()...)),
		)

		missingTxs, err := s.getSubtreeMissingTxs(t.Context(), *subtree.RootHash(), subtree, unresolved, []chainhash.Hash{}, "http://localhost:8000")
		require.NoError(t, err, "should be no error since all txs are in the subtree")

		require.Len(t, missingTxs, 2, "should be 2 missing txs since all txs are in the subtree")

		// validate the txs are correct
		assert.Equal(t, *hash1, *missingTxs[0].tx.TxIDChainHash())
		assert.Equal(t, *hash2, *missingTxs[1].tx.TxIDChainHash())
	})

	t.Run("getSubtreeMissingTxs - from data", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash1,
		}, {
			Hash: *hash2,
		}}

		subtreeDataBytes := make([]byte, 0, 2048)
		subtreeDataBytes = append(subtreeDataBytes, tx1.ExtendedBytes()...)
		subtreeDataBytes = append(subtreeDataBytes, tx2.ExtendedBytes()...)
		subtreeDataBytes = append(subtreeDataBytes, tx3.ExtendedBytes()...)
		subtreeDataBytes = append(subtreeDataBytes, tx4.ExtendedBytes()...)

		err = subtreeStore.Set(t.Context(),
			subtree.RootHash()[:],
			fileformat.FileTypeSubtreeData,
			subtreeDataBytes,
		)
		require.NoError(t, err)

		allTxs := []chainhash.Hash{
			*hash1,
			*hash2,
			*hash3,
			*hash4,
		}

		missingTxs, err := s.getSubtreeMissingTxs(t.Context(), *subtree.RootHash(), subtree, unresolved, allTxs, "test")
		require.NoError(t, err, "should be no error since all txs are in the subtree")

		require.Len(t, missingTxs, 2, "should be 2 missing txs since all txs are in the subtree")

		// validate the txs are correct
		assert.Equal(t, *hash1, *missingTxs[0].tx.TxIDChainHash())
		assert.Equal(t, *hash2, *missingTxs[1].tx.TxIDChainHash())
	})

	t.Run("getSubtreeMissingTxs - from data with coinbase & odd number of txs", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		coinbaseSubtree, err := subtreepkg.NewIncompleteTreeByLeafCount(3)
		require.NoError(t, err)
		require.NoError(t, coinbaseSubtree.AddCoinbaseNode())
		require.NoError(t, coinbaseSubtree.AddNode(*hash2, 122, 2))
		require.NoError(t, coinbaseSubtree.AddNode(*hash3, 123, 3))

		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash2,
		}}

		subtreeDataBytes := make([]byte, 0, 2048)
		subtreeDataBytes = append(subtreeDataBytes, tx2.ExtendedBytes()...)
		subtreeDataBytes = append(subtreeDataBytes, tx3.ExtendedBytes()...)

		err = subtreeStore.Set(t.Context(),
			coinbaseSubtree.RootHash()[:],
			fileformat.FileTypeSubtreeData,
			subtreeDataBytes,
		)
		require.NoError(t, err)

		allTxs := []chainhash.Hash{
			subtreepkg.CoinbasePlaceholderHashValue,
			*hash2,
			*hash3,
		}

		missingTxs, err := s.getSubtreeMissingTxs(t.Context(), *coinbaseSubtree.RootHash(), coinbaseSubtree, unresolved, allTxs, "test")
		require.NoError(t, err, "should be no error since all txs are in the subtree")

		require.Len(t, missingTxs, 1, "should be 1 missing txs since all txs are in the subtree")

		// validate the txs are correct
		assert.Equal(t, *hash2, *missingTxs[0].tx.TxIDChainHash())
	})

	t.Run("getSubtreeMissingTxs - from data with tx mismatch", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		coinbaseSubtree, err := subtreepkg.NewIncompleteTreeByLeafCount(3)
		require.NoError(t, err)
		require.NoError(t, coinbaseSubtree.AddCoinbaseNode())
		require.NoError(t, coinbaseSubtree.AddNode(*hash2, 122, 2))
		require.NoError(t, coinbaseSubtree.AddNode(*hash3, 123, 3))

		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash2,
		}}

		subtreeDataBytes := make([]byte, 0, 2048)
		subtreeDataBytes = append(subtreeDataBytes, tx3.ExtendedBytes()...)
		subtreeDataBytes = append(subtreeDataBytes, tx2.ExtendedBytes()...)

		err = subtreeStore.Set(t.Context(),
			coinbaseSubtree.RootHash()[:],
			fileformat.FileTypeSubtreeData,
			subtreeDataBytes,
		)
		require.NoError(t, err)

		allTxs := []chainhash.Hash{
			subtreepkg.CoinbasePlaceholderHashValue,
			*hash2,
			*hash3,
		}

		_, err = s.getSubtreeMissingTxs(t.Context(), *coinbaseSubtree.RootHash(), coinbaseSubtree, unresolved, allTxs, "test")
		require.Error(t, err, "should be an error since txs are in the wrong order")
	})

	t.Run("getSubtreeMissingTxs - from data with tx missing", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		coinbaseSubtree, err := subtreepkg.NewIncompleteTreeByLeafCount(3)
		require.NoError(t, err)
		require.NoError(t, coinbaseSubtree.AddCoinbaseNode())
		require.NoError(t, coinbaseSubtree.AddNode(*hash2, 122, 2))
		require.NoError(t, coinbaseSubtree.AddNode(*hash3, 123, 3))

		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash2,
		}}

		subtreeDataBytes := make([]byte, 0, 2048)
		subtreeDataBytes = append(subtreeDataBytes, tx2.ExtendedBytes()...)
		// tx3 is missing to simulate a mismatch
		// subtreeDataBytes = append(subtreeDataBytes, tx3.ExtendedBytes()...)

		err = subtreeStore.Set(t.Context(),
			coinbaseSubtree.RootHash()[:],
			fileformat.FileTypeSubtreeData,
			subtreeDataBytes,
		)
		require.NoError(t, err)

		allTxs := []chainhash.Hash{
			subtreepkg.CoinbasePlaceholderHashValue,
			*hash2,
			*hash3,
		}

		_, err = s.getSubtreeMissingTxs(t.Context(), *coinbaseSubtree.RootHash(), coinbaseSubtree, unresolved, allTxs, "test")
		require.Error(t, err, "should be an error since we are missing a tx")
	})
}

func Test_getSubtreeMissingTxs_testnet(t *testing.T) {
	tSettings := test.CreateBaseTestSettings(t)

	t.Run("getSubtreeMissingTxs - from testnet", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		subtreeHashStr := "8ddeb634b22bcb3b5bbebdd74fdc4a4e2bfd92dc4a3f4c2ed43883607ab6a68a"

		subtreeHashBytes, err := hex.DecodeString(subtreeHashStr)
		require.NoError(t, err)

		subtreeHash := chainhash.Hash(subtreeHashBytes)

		subtreeNodeBytes, err := hex.DecodeString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5335baa155f4fbfca2d0d422c150472a4fff9f858a47c3ebb8a7db51c8187273567e0824cb889ec1d55cf9c39bf83d254623d25f9bd22f3ca1dcc4a21196a55e")
		require.NoError(t, err)

		testSubtree, err := subtreepkg.NewIncompleteTreeByLeafCount(3)
		require.NoError(t, err)

		_ = testSubtree.AddCoinbaseNode()

		allTxs := make([]chainhash.Hash, 0)
		for i := 0; i < len(subtreeNodeBytes)/chainhash.HashSize; i++ {
			allTxs = append(allTxs, chainhash.Hash(subtreeNodeBytes[i*chainhash.HashSize:(i+1)*chainhash.HashSize]))

			_ = testSubtree.AddNode(chainhash.Hash(subtreeNodeBytes[i*chainhash.HashSize:(i+1)*chainhash.HashSize]), 1, 1)
		}

		subtreeDataBytes, err := os.ReadFile(fmt.Sprintf("testdata/%s.subtreeData", subtreeHashStr))
		require.NoError(t, err)

		subtreeDataReader := bytes.NewReader(subtreeDataBytes)

		subtreeData, err := subtreepkg.NewSubtreeDataFromReader(testSubtree, subtreeDataReader)
		require.NoError(t, err)

		_ = subtreeData

		err = subtreeStore.Set(
			t.Context(),
			subtreeHash[:],
			fileformat.FileTypeSubtreeData,
			subtreeDataBytes,
		)
		require.NoError(t, err)

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		unresolved := make([]utxo.UnresolvedMetaData, 0)
		for idx, txHash := range allTxs {
			unresolved = append(unresolved, utxo.UnresolvedMetaData{
				Hash: txHash,
				Idx:  idx,
			})
		}

		missingTxs, err := s.getSubtreeMissingTxs(t.Context(), subtreeHash, nil, unresolved, allTxs, "test")
		require.NoError(t, err, "should be no error since all txs are in the subtree")

		require.Len(t, missingTxs, 3, "should be 3 missing txs since all txs are in the subtree")

		// validate the txs are correct
		// cannot check coinbase assert.Equal(t, allTxs[0], *missingTxs[0].tx.TxIDChainHash())
		assert.Equal(t, allTxs[1], *missingTxs[1].tx.TxIDChainHash())
		assert.Equal(t, allTxs[2], *missingTxs[2].tx.TxIDChainHash())
	})

	t.Run("getSubtreeMissingTxs - from testnet", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		subtreeHashStr := "e973de16e606750feed45d7b02dcf493887ef27acabbd7913710b9f3454f2555"

		subtreeHashBytes, err := hex.DecodeString(subtreeHashStr)
		require.NoError(t, err)

		subtreeHash := chainhash.Hash(subtreeHashBytes)

		subtreeNodeBytes, err := os.ReadFile(fmt.Sprintf("testdata/%s.subtree", subtreeHashStr))
		require.NoError(t, err)

		testSubtree, err := subtreepkg.NewIncompleteTreeByLeafCount(len(subtreeNodeBytes) / chainhash.HashSize)
		require.NoError(t, err)

		allTxs := make([]chainhash.Hash, 0)
		for i := 0; i < len(subtreeNodeBytes)/chainhash.HashSize; i++ {
			allTxs = append(allTxs, chainhash.Hash(subtreeNodeBytes[i*chainhash.HashSize:(i+1)*chainhash.HashSize]))

			_ = testSubtree.AddNode(chainhash.Hash(subtreeNodeBytes[i*chainhash.HashSize:(i+1)*chainhash.HashSize]), 1, 1)
		}

		subtreeDataBytes, err := os.ReadFile(fmt.Sprintf("testdata/%s.subtreeData", subtreeHashStr))
		require.NoError(t, err)

		subtreeDataReader := bytes.NewReader(subtreeDataBytes)

		subtreeData, err := subtreepkg.NewSubtreeDataFromReader(testSubtree, subtreeDataReader)
		require.NoError(t, err)

		_ = subtreeData

		err = subtreeStore.Set(
			t.Context(),
			subtreeHash[:],
			fileformat.FileTypeSubtreeData,
			subtreeDataBytes,
		)
		require.NoError(t, err)

		// Create a mock Server struct
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		unresolved := make([]utxo.UnresolvedMetaData, 0)
		for idx, txHash := range allTxs {
			unresolved = append(unresolved, utxo.UnresolvedMetaData{
				Hash: txHash,
				Idx:  idx,
			})
		}

		missingTxs, err := s.getSubtreeMissingTxs(t.Context(), subtreeHash, nil, unresolved, allTxs, "test")
		require.NoError(t, err, "should be no error since all txs are in the subtree")

		require.Len(t, missingTxs, 1024, "should be 1024 missing txs since all txs are in the subtree")

		// validate the txs are correct
		for i := 0; i < len(missingTxs); i++ {
			assert.Equal(t, allTxs[i], *missingTxs[i].tx.TxIDChainHash())
		}
	})
}

func Test_getSubtreeMissingTxs_InvalidSubtreeData(t *testing.T) {
	tSettings := test.CreateBaseTestSettings(t)

	t.Run("getSubtreeMissingTxs - handles invalid subtree data gracefully", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a subtree with 3 transactions
		subtree, err := subtreepkg.NewIncompleteTreeByLeafCount(3)
		require.NoError(t, err)
		require.NoError(t, subtree.AddCoinbaseNode())
		require.NoError(t, subtree.AddNode(*hash2, 122, 2))
		require.NoError(t, subtree.AddNode(*hash3, 123, 3))

		subtreeHash := subtree.RootHash()

		// Create URLs to fetch from
		urls := []string{
			"http://test1.example.com/subtree/" + subtreeHash.String() + "/data",
			"http://test2.example.com/subtree/" + subtreeHash.String() + "/data",
		}

		// Register mock HTTP responses that return invalid/corrupted subtree data
		// This will cause NewSubtreeDataFromReader to fail
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()

		// First URL returns invalid data that will cause NewSubtreeDataFromReader to fail
		httpmock.RegisterResponder(
			"GET",
			urls[0],
			httpmock.NewBytesResponder(200, []byte("invalid subtree data")),
		)

		// Second URL returns valid subtree data
		subtreeDataBytes := make([]byte, 0, 2048)
		subtreeDataBytes = append(subtreeDataBytes, tx2.ExtendedBytes()...)
		subtreeDataBytes = append(subtreeDataBytes, tx3.ExtendedBytes()...)

		httpmock.RegisterResponder(
			"GET",
			urls[1],
			httpmock.NewBytesResponder(200, subtreeDataBytes),
		)

		// Create the Server instance
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		// Create unresolved metadata
		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash2,
			Idx:  0,
		}}

		allTxs := []chainhash.Hash{
			subtreepkg.CoinbasePlaceholderHashValue,
			*hash2,
			*hash3,
		}

		// Mock the URLs in a way that getSubtreeMissingTxs can access them

		// The function should not panic even when NewSubtreeDataFromReader fails for the first URL
		// It should continue to the next URL
		_, err = s.getSubtreeMissingTxs(context.Background(), *subtreeHash, subtree, unresolved, allTxs, "test")

		// The test passes if there's no panic
		// Since we're testing error handling, we expect either success (if second URL works)
		// or an error (if all URLs fail), but no panic
		t.Logf("Test completed without panic. Error (if any): %v", err)
	})

	t.Run("getSubtreeMissingTxs - handles all invalid URLs gracefully", func(t *testing.T) {
		txMetaStore, validatorClient, _, subtreeStore, blockchainClient, deferFunc := setup(t)
		defer deferFunc()

		// Create a subtree
		subtree, err := subtreepkg.NewIncompleteTreeByLeafCount(3)
		require.NoError(t, err)
		require.NoError(t, subtree.AddCoinbaseNode())
		require.NoError(t, subtree.AddNode(*hash2, 122, 2))
		require.NoError(t, subtree.AddNode(*hash3, 123, 3))

		subtreeHash := subtree.RootHash()

		// Mock HTTP to return invalid data
		httpmock.Activate()
		defer httpmock.DeactivateAndReset()

		// Register a catch-all responder that returns invalid data
		httpmock.RegisterResponder(
			"GET",
			`=~^.*$`,
			httpmock.NewBytesResponder(200, []byte("invalid data")),
		)

		// Create the Server instance
		s := &Server{
			logger:           ulogger.TestLogger{},
			settings:         tSettings,
			utxoStore:        txMetaStore,
			subtreeStore:     subtreeStore,
			validatorClient:  validatorClient,
			blockchainClient: blockchainClient,
		}

		unresolved := []utxo.UnresolvedMetaData{{
			Hash: *hash2,
			Idx:  0,
		}}

		allTxs := []chainhash.Hash{
			subtreepkg.CoinbasePlaceholderHashValue,
			*hash2,
			*hash3,
		}

		// This should not panic, even though all URLs return invalid data
		_, err = s.getSubtreeMissingTxs(context.Background(), *subtreeHash, subtree, unresolved, allTxs, "test")

		// We expect an error since no valid data could be retrieved
		// The important thing is that it doesn't panic
		t.Logf("Test completed without panic. Error (if any): %v", err)
	})
}
