// 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 (
	"testing"

	"dappco.re/go/core/blockchain/types"
	"dappco.re/go/core/blockchain/wire"
)

func TestGetRingOutputs_Good(t *testing.T) {
	c := newTestChain(t)

	pubKey := types.PublicKey{1, 2, 3}
	tx := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 0}},
		Vout: []types.TxOutput{
			types.TxOutputBare{
				Amount: 1000,
				Target: types.TxOutToKey{Key: pubKey, MixAttr: 0},
			},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	txHash := wire.TransactionHash(&tx)

	err := c.PutTransaction(txHash, &tx, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}})
	if err != nil {
		t.Fatalf("PutTransaction: %v", err)
	}

	gidx, err := c.PutOutput(1000, txHash, 0)
	if err != nil {
		t.Fatalf("PutOutput: %v", err)
	}
	if gidx != 0 {
		t.Fatalf("gidx: got %d, want 0", gidx)
	}

	pubs, err := c.GetRingOutputs(100, 1000, []uint64{0})
	if err != nil {
		t.Fatalf("GetRingOutputs: %v", err)
	}
	if len(pubs) != 1 {
		t.Fatalf("pubs length: got %d, want 1", len(pubs))
	}
	if pubs[0] != pubKey {
		t.Errorf("pubs[0]: got %x, want %x", pubs[0], pubKey)
	}
}

func TestGetRingOutputs_Good_Multisig(t *testing.T) {
	c := newTestChain(t)

	first := types.PublicKey{0x11, 0x22, 0x33}
	second := types.PublicKey{0x44, 0x55, 0x66}
	tx := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 0}},
		Vout: []types.TxOutput{
			types.TxOutputBare{
				Amount: 1000,
				Target: types.TxOutMultisig{
					MinimumSigs: 2,
					Keys:        []types.PublicKey{first, second},
				},
			},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	txHash := wire.TransactionHash(&tx)

	if err := c.PutTransaction(txHash, &tx, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}}); err != nil {
		t.Fatalf("PutTransaction: %v", err)
	}
	if _, err := c.PutOutput(1000, txHash, 0); err != nil {
		t.Fatalf("PutOutput: %v", err)
	}

	pubs, err := c.GetRingOutputs(100, 1000, []uint64{0})
	if err != nil {
		t.Fatalf("GetRingOutputs: %v", err)
	}
	if pubs[0] != first {
		t.Errorf("pubs[0]: got %x, want %x", pubs[0], first)
	}
}

func TestGetRingOutputs_Good_HTLC(t *testing.T) {
	c := newTestChain(t)

	redeem := types.PublicKey{0xAA, 0xBB, 0xCC}
	refund := types.PublicKey{0xDD, 0xEE, 0xFF}
	tx := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 0}},
		Vout: []types.TxOutput{
			types.TxOutputBare{
				Amount: 1000,
				Target: types.TxOutHTLC{
					HTLCHash:   types.Hash{0x01},
					Flags:      0,
					Expiration: 200,
					PKRedeem:   redeem,
					PKRefund:   refund,
				},
			},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	txHash := wire.TransactionHash(&tx)

	if err := c.PutTransaction(txHash, &tx, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}}); err != nil {
		t.Fatalf("PutTransaction: %v", err)
	}
	if _, err := c.PutOutput(1000, txHash, 0); err != nil {
		t.Fatalf("PutOutput: %v", err)
	}

	pubs, err := c.GetRingOutputs(100, 1000, []uint64{0})
	if err != nil {
		t.Fatalf("GetRingOutputs: %v", err)
	}
	if pubs[0] != redeem {
		t.Errorf("pubs[0]: got %x, want %x", pubs[0], redeem)
	}

	pubs, err = c.GetRingOutputs(250, 1000, []uint64{0})
	if err != nil {
		t.Fatalf("GetRingOutputs refund path: %v", err)
	}
	if pubs[0] != refund {
		t.Errorf("pubs[0] refund path: got %x, want %x", pubs[0], refund)
	}
}

func TestGetRingOutputs_Good_HTLCExpirationBoundary(t *testing.T) {
	c := newTestChain(t)

	redeem := types.PublicKey{0xAA, 0xBB, 0xCC}
	refund := types.PublicKey{0xDD, 0xEE, 0xFF}
	tx := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 0}},
		Vout: []types.TxOutput{
			types.TxOutputBare{
				Amount: 1000,
				Target: types.TxOutHTLC{
					HTLCHash:   types.Hash{0x01},
					Flags:      0,
					Expiration: 200,
					PKRedeem:   redeem,
					PKRefund:   refund,
				},
			},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	txHash := wire.TransactionHash(&tx)

	if err := c.PutTransaction(txHash, &tx, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}}); err != nil {
		t.Fatalf("PutTransaction: %v", err)
	}
	if _, err := c.PutOutput(1000, txHash, 0); err != nil {
		t.Fatalf("PutOutput: %v", err)
	}

	pubs, err := c.GetRingOutputs(200, 1000, []uint64{0})
	if err != nil {
		t.Fatalf("GetRingOutputs boundary path: %v", err)
	}
	if pubs[0] != redeem {
		t.Errorf("pubs[0] boundary path: got %x, want %x", pubs[0], redeem)
	}
}

func TestGetRingOutputs_Good_MultipleOutputs(t *testing.T) {
	c := newTestChain(t)

	key1 := types.PublicKey{0x11}
	key2 := types.PublicKey{0x22}

	tx1 := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 0}},
		Vout: []types.TxOutput{
			types.TxOutputBare{Amount: 500, Target: types.TxOutToKey{Key: key1}},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	tx1Hash := wire.TransactionHash(&tx1)

	tx2 := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 1}},
		Vout: []types.TxOutput{
			types.TxOutputBare{Amount: 500, Target: types.TxOutToKey{Key: key2}},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	tx2Hash := wire.TransactionHash(&tx2)

	if err := c.PutTransaction(tx1Hash, &tx1, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}}); err != nil {
		t.Fatalf("PutTransaction(tx1): %v", err)
	}
	if err := c.PutTransaction(tx2Hash, &tx2, &TxMeta{KeeperBlock: 1, GlobalOutputIndexes: []uint64{1}}); err != nil {
		t.Fatalf("PutTransaction(tx2): %v", err)
	}

	if _, err := c.PutOutput(500, tx1Hash, 0); err != nil {
		t.Fatalf("PutOutput(tx1): %v", err)
	}
	if _, err := c.PutOutput(500, tx2Hash, 0); err != nil {
		t.Fatalf("PutOutput(tx2): %v", err)
	}

	pubs, err := c.GetRingOutputs(500, 500, []uint64{0, 1})
	if err != nil {
		t.Fatalf("GetRingOutputs: %v", err)
	}
	if len(pubs) != 2 {
		t.Fatalf("pubs length: got %d, want 2", len(pubs))
	}
	if pubs[0] != key1 {
		t.Errorf("pubs[0]: got %x, want %x", pubs[0], key1)
	}
	if pubs[1] != key2 {
		t.Errorf("pubs[1]: got %x, want %x", pubs[1], key2)
	}
}

func TestGetRingOutputs_Bad_OutputNotFound(t *testing.T) {
	c := newTestChain(t)

	_, err := c.GetRingOutputs(1000, 1000, []uint64{99})
	if err == nil {
		t.Fatal("GetRingOutputs: expected error for missing output, got nil")
	}
}

func TestGetRingOutputs_Bad_TxNotFound(t *testing.T) {
	c := newTestChain(t)

	// Index an output pointing to a transaction that does not exist in the store.
	fakeTxHash := types.Hash{0xde, 0xad}
	if _, err := c.PutOutput(1000, fakeTxHash, 0); err != nil {
		t.Fatalf("PutOutput: %v", err)
	}

	_, err := c.GetRingOutputs(1000, 1000, []uint64{0})
	if err == nil {
		t.Fatal("GetRingOutputs: expected error for missing tx, got nil")
	}
}

func TestGetRingOutputs_Bad_OutputIndexOutOfRange(t *testing.T) {
	c := newTestChain(t)

	tx := types.Transaction{
		Version: types.VersionPreHF4,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: 0}},
		Vout: []types.TxOutput{
			types.TxOutputBare{Amount: 1000, Target: types.TxOutToKey{Key: types.PublicKey{0x01}}},
		},
		Extra:      wire.EncodeVarint(0),
		Attachment: wire.EncodeVarint(0),
	}
	txHash := wire.TransactionHash(&tx)

	if err := c.PutTransaction(txHash, &tx, &TxMeta{KeeperBlock: 0, GlobalOutputIndexes: []uint64{0}}); err != nil {
		t.Fatalf("PutTransaction: %v", err)
	}

	// Index with outNo=5, which is beyond the transaction's single output.
	if _, err := c.PutOutput(1000, txHash, 5); err != nil {
		t.Fatalf("PutOutput: %v", err)
	}

	_, err := c.GetRingOutputs(1000, 1000, []uint64{0})
	if err == nil {
		t.Fatal("GetRingOutputs: expected error for out-of-range index, got nil")
	}
}

func TestGetRingOutputs_Good_EmptyOffsets(t *testing.T) {
	c := newTestChain(t)

	pubs, err := c.GetRingOutputs(1000, 1000, []uint64{})
	if err != nil {
		t.Fatalf("GetRingOutputs: %v", err)
	}
	if len(pubs) != 0 {
		t.Errorf("pubs length: got %d, want 0", len(pubs))
	}
}
