// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
//     https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// SPDX-License-Identifier: EUPL-1.2

package wallet

import (
	"testing"

	"dappco.re/go/core/blockchain/chain"
	"dappco.re/go/core/blockchain/crypto"
	"dappco.re/go/core/blockchain/types"
	"dappco.re/go/core/blockchain/wire"
	store "dappco.re/go/core/store"
)

func makeTestBlock(t *testing.T, height uint64, prevHash types.Hash,
	destAccount *Account) (*types.Block, types.Hash) {
	t.Helper()

	txPub, txSec, _ := crypto.GenerateKeys()
	derivation, _ := crypto.GenerateKeyDerivation(
		[32]byte(destAccount.ViewPublicKey), txSec)
	ephPub, _ := crypto.DerivePublicKey(
		derivation, 0, [32]byte(destAccount.SpendPublicKey))

	minerTx := types.Transaction{
		Version: 0,
		Vin:     []types.TxInput{types.TxInputGenesis{Height: height}},
		Vout: []types.TxOutput{
			types.TxOutputBare{
				Amount: 1_000_000_000_000, // 1 LTHN
				Target: types.TxOutToKey{Key: types.PublicKey(ephPub)},
			},
		},
		Extra:      BuildTxExtra(types.PublicKey(txPub)),
		Attachment: wire.EncodeVarint(0),
	}
	minerTx.Signatures = [][]types.Signature{{}}

	blk := &types.Block{
		BlockHeader: types.BlockHeader{
			MajorVersion: 1,
			Timestamp:    1770897600 + height*120,
			PrevID:       prevHash,
		},
		MinerTx: minerTx,
	}

	hash := wire.BlockHash(blk)
	return blk, hash
}

func TestWalletSyncAndBalance(t *testing.T) {
	s, err := store.New(":memory:")
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	acc, err := GenerateAccount()
	if err != nil {
		t.Fatal(err)
	}
	c := chain.New(s)

	// Store 3 blocks, each paying 1 LTHN to our account.
	var prevHash types.Hash
	for h := uint64(0); h < 3; h++ {
		blk, hash := makeTestBlock(t, h, prevHash, acc)
		meta := &chain.BlockMeta{Hash: hash, Height: h}
		if err := c.PutBlock(blk, meta); err != nil {
			t.Fatal(err)
		}
		// Index the output.
		txHash := wire.TransactionHash(&blk.MinerTx)
		c.PutOutput(1_000_000_000_000, txHash, 0)
		prevHash = hash
	}

	w := NewWallet(acc, s, c, nil)
	if err := w.Sync(); err != nil {
		t.Fatal(err)
	}

	confirmed, locked, err := w.Balance()
	if err != nil {
		t.Fatal(err)
	}

	// All 3 blocks are coinbase with MinedMoneyUnlockWindow=10.
	// Chain height = 3, so all 3 are locked (height + 10 > 3).
	if locked != 3_000_000_000_000 {
		t.Fatalf("locked = %d, want 3_000_000_000_000", locked)
	}
	if confirmed != 0 {
		t.Fatalf("confirmed = %d, want 0 (all locked)", confirmed)
	}
}

func TestWalletTransfers(t *testing.T) {
	s, err := store.New(":memory:")
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	acc, err := GenerateAccount()
	if err != nil {
		t.Fatal(err)
	}
	c := chain.New(s)

	blk, hash := makeTestBlock(t, 0, types.Hash{}, acc)
	meta := &chain.BlockMeta{Hash: hash, Height: 0}
	c.PutBlock(blk, meta)
	txHash := wire.TransactionHash(&blk.MinerTx)
	c.PutOutput(1_000_000_000_000, txHash, 0)

	w := NewWallet(acc, s, c, nil)
	w.Sync()

	transfers, err := w.Transfers()
	if err != nil {
		t.Fatal(err)
	}
	if len(transfers) != 1 {
		t.Fatalf("got %d transfers, want 1", len(transfers))
	}
}

func TestWalletScanTxMarksHTLCSpend(t *testing.T) {
	s, err := store.New(":memory:")
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()

	acc, err := GenerateAccount()
	if err != nil {
		t.Fatal(err)
	}

	ki := types.KeyImage{0x42}
	if err := putTransfer(s, &Transfer{
		KeyImage:    ki,
		Amount:      100,
		BlockHeight: 1,
	}); err != nil {
		t.Fatal(err)
	}

	w := &Wallet{
		store:   s,
		scanner: NewV1Scanner(acc),
	}

	tx := &types.Transaction{
		Version: types.VersionPreHF4,
		Vin: []types.TxInput{
			types.TxInputHTLC{
				Amount:   100,
				KeyImage: ki,
			},
		},
	}

	if err := w.scanTx(tx, 10); err != nil {
		t.Fatal(err)
	}

	got, err := getTransfer(s, ki)
	if err != nil {
		t.Fatal(err)
	}
	if !got.Spent {
		t.Fatal("expected HTLC spend to be marked spent")
	}
	if got.SpentHeight != 10 {
		t.Fatalf("spent height = %d, want 10", got.SpentHeight)
	}
}
