// 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 wallet

import (
	"testing"

	store "dappco.re/go/core/store"

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

func newTestStore(t *testing.T) *store.Store {
	t.Helper()
	s, err := store.New(":memory:")
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { s.Close() })
	return s
}

func TestTransferPutGet(t *testing.T) {
	s := newTestStore(t)

	var ki types.KeyImage
	ki[0] = 0x42
	tr := Transfer{
		TxHash:      types.Hash{1},
		OutputIndex: 0,
		Amount:      1000,
		BlockHeight: 10,
		KeyImage:    ki,
	}

	if err := putTransfer(s, &tr); err != nil {
		t.Fatal(err)
	}

	got, err := getTransfer(s, ki)
	if err != nil {
		t.Fatal(err)
	}
	if got.Amount != 1000 {
		t.Fatalf("amount = %d, want 1000", got.Amount)
	}
	if got.TxHash != tr.TxHash {
		t.Fatalf("tx hash mismatch: got %s", got.TxHash)
	}
	if got.KeyImage != ki {
		t.Fatalf("key image mismatch: got %s", got.KeyImage)
	}
}

func TestTransferGetNotFound(t *testing.T) {
	s := newTestStore(t)

	var ki types.KeyImage
	ki[0] = 0xFF
	_, err := getTransfer(s, ki)
	if err == nil {
		t.Fatal("expected error for missing transfer")
	}
}

func TestTransferOverwrite(t *testing.T) {
	s := newTestStore(t)

	var ki types.KeyImage
	ki[0] = 0x01
	tr := Transfer{Amount: 500, BlockHeight: 5, KeyImage: ki}
	if err := putTransfer(s, &tr); err != nil {
		t.Fatal(err)
	}

	tr.Amount = 999
	if err := putTransfer(s, &tr); err != nil {
		t.Fatal(err)
	}

	got, err := getTransfer(s, ki)
	if err != nil {
		t.Fatal(err)
	}
	if got.Amount != 999 {
		t.Fatalf("amount = %d, want 999 after overwrite", got.Amount)
	}
}

func TestTransferMarkSpent(t *testing.T) {
	s := newTestStore(t)

	var ki types.KeyImage
	ki[0] = 0x43
	tr := Transfer{Amount: 500, BlockHeight: 5, KeyImage: ki}
	if err := putTransfer(s, &tr); err != nil {
		t.Fatal(err)
	}

	if err := markTransferSpent(s, ki, 20); err != nil {
		t.Fatal(err)
	}

	got, err := getTransfer(s, ki)
	if err != nil {
		t.Fatal(err)
	}
	if !got.Spent {
		t.Fatal("should be spent")
	}
	if got.SpentHeight != 20 {
		t.Fatalf("spent height = %d, want 20", got.SpentHeight)
	}
}

func TestTransferMarkSpentNotFound(t *testing.T) {
	s := newTestStore(t)

	var ki types.KeyImage
	ki[0] = 0xDE
	if err := markTransferSpent(s, ki, 10); err == nil {
		t.Fatal("expected error marking non-existent transfer as spent")
	}
}

func TestTransferList(t *testing.T) {
	s := newTestStore(t)

	for i := byte(0); i < 3; i++ {
		var ki types.KeyImage
		ki[0] = i + 1
		tr := Transfer{
			Amount:      uint64(i+1) * 100,
			BlockHeight: uint64(i),
			KeyImage:    ki,
		}
		if err := putTransfer(s, &tr); err != nil {
			t.Fatal(err)
		}
	}

	// Mark second transfer as spent.
	var ki types.KeyImage
	ki[0] = 2
	if err := markTransferSpent(s, ki, 10); err != nil {
		t.Fatal(err)
	}

	transfers, err := listTransfers(s)
	if err != nil {
		t.Fatal(err)
	}
	if len(transfers) != 3 {
		t.Fatalf("got %d transfers, want 3", len(transfers))
	}

	unspent := 0
	for _, tr := range transfers {
		if !tr.Spent {
			unspent++
		}
	}
	if unspent != 2 {
		t.Fatalf("got %d unspent, want 2", unspent)
	}
}

func TestTransferListEmpty(t *testing.T) {
	s := newTestStore(t)

	transfers, err := listTransfers(s)
	if err != nil {
		t.Fatal(err)
	}
	if len(transfers) != 0 {
		t.Fatalf("got %d transfers, want 0 for empty store", len(transfers))
	}
}

func TestTransferSpendableBasic(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5}
	if !tr.IsSpendable(20, false) {
		t.Fatal("unspent transfer should be spendable")
	}
}

func TestTransferSpendableSpent(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5, Spent: true}
	if tr.IsSpendable(20, false) {
		t.Fatal("spent transfer should not be spendable")
	}
}

func TestTransferSpendableCoinbaseImmature(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5, Coinbase: true}
	// MinedMoneyUnlockWindow is 10, so block 5 + 10 = 15 > 10.
	if tr.IsSpendable(10, false) {
		t.Fatal("immature coinbase should not be spendable")
	}
}

func TestTransferSpendableCoinbaseMature(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5, Coinbase: true}
	// MinedMoneyUnlockWindow is 10, so block 5 + 10 = 15 <= 20.
	if !tr.IsSpendable(20, false) {
		t.Fatal("mature coinbase should be spendable")
	}
}

func TestTransferSpendableCoinbaseBoundary(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5, Coinbase: true}
	// Exact boundary: 5 + 10 = 15 == 15, not greater, so spendable.
	if !tr.IsSpendable(15, false) {
		t.Fatal("coinbase at exact maturity boundary should be spendable")
	}
}

func TestTransferSpendableUnlockTime(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5, UnlockTime: 50}
	if tr.IsSpendable(30, false) {
		t.Fatal("transfer with future unlock time should not be spendable")
	}
	if !tr.IsSpendable(50, false) {
		t.Fatal("transfer at exact unlock height should be spendable")
	}
	if !tr.IsSpendable(100, false) {
		t.Fatal("transfer past unlock height should be spendable")
	}
}

func TestTransferSpendableUnlockTimeZero(t *testing.T) {
	tr := Transfer{Amount: 1000, BlockHeight: 5, UnlockTime: 0}
	if !tr.IsSpendable(1, false) {
		t.Fatal("transfer with zero unlock time should be spendable")
	}
}

func TestTransferKeyPairFields(t *testing.T) {
	s := newTestStore(t)

	var ki types.KeyImage
	ki[0] = 0x99
	var pub types.PublicKey
	pub[0] = 0xAA
	var sec types.SecretKey
	sec[0] = 0xBB

	tr := Transfer{
		Amount:       2000,
		BlockHeight:  50,
		KeyImage:     ki,
		EphemeralKey: KeyPair{Public: pub, Secret: sec},
	}

	if err := putTransfer(s, &tr); err != nil {
		t.Fatal(err)
	}

	got, err := getTransfer(s, ki)
	if err != nil {
		t.Fatal(err)
	}
	if got.EphemeralKey.Public != pub {
		t.Fatal("ephemeral public key mismatch")
	}
	if got.EphemeralKey.Secret != sec {
		t.Fatal("ephemeral secret key mismatch")
	}
}
