package native_test

import (
	"testing"
	"time"

	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
	"github.com/stretchr/testify/require"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
	"git.marketally.com/tutus-one/tutus-chain/pkg/tutustest"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

func newTributeClient(t *testing.T) *tutustest.ContractInvoker {
	return newNativeClient(t, nativenames.Tribute)
}

// TestTribute_GetConfig tests the getConfig method.
func TestTribute_GetConfig(t *testing.T) {
	c := newTributeClient(t)

	// Get default config
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		arr, ok := stack[0].Value().([]stackitem.Item)
		require.True(t, ok, "expected array result")
		require.GreaterOrEqual(t, len(arr), 15) // TributeConfig has 15 fields
	}, "getConfig")
}

// TestTribute_GetTotalAccounts tests the getTotalAccounts method.
func TestTribute_GetTotalAccounts(t *testing.T) {
	c := newTributeClient(t)

	// Initially should be 0
	c.Invoke(t, 0, "getTotalAccounts")
}

// TestTribute_GetTotalAssessments tests the getTotalAssessments method.
func TestTribute_GetTotalAssessments(t *testing.T) {
	c := newTributeClient(t)

	// Initially should be 0
	c.Invoke(t, 0, "getTotalAssessments")
}

// TestTribute_GetTotalIncentives tests the getTotalIncentives method.
func TestTribute_GetTotalIncentives(t *testing.T) {
	c := newTributeClient(t)

	// Initially should be 0
	c.Invoke(t, 0, "getTotalIncentives")
}

// TestTribute_GetTotalRedistributions tests the getTotalRedistributions method.
func TestTribute_GetTotalRedistributions(t *testing.T) {
	c := newTributeClient(t)

	// Initially should be 0
	c.Invoke(t, 0, "getTotalRedistributions")
}

// TestTribute_GetTributePool tests the getTributePool method.
func TestTribute_GetTributePool(t *testing.T) {
	c := newTributeClient(t)

	// Initially should be 0
	c.Invoke(t, 0, "getTributePool")
}

// TestTribute_GetAccount_NonExistent tests getting a non-existent account.
func TestTribute_GetAccount_NonExistent(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent account should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent account")
	}, "getAccount", acc.ScriptHash())
}

// TestTribute_GetVelocity_NonExistent tests getting velocity for non-existent account.
func TestTribute_GetVelocity_NonExistent(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent account should return 0 velocity
	c.Invoke(t, 0, "getVelocity", acc.ScriptHash())
}

// TestTribute_GetHoardingLevel_NonExistent tests getting hoarding level for non-existent account.
func TestTribute_GetHoardingLevel_NonExistent(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent account should return 0 (HoardingNone)
	c.Invoke(t, 0, "getHoardingLevel", acc.ScriptHash())
}

// TestTribute_IsExempt_NonExistent tests checking exemption for non-existent account.
func TestTribute_IsExempt_NonExistent(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent account should return false
	c.Invoke(t, false, "isExempt", acc.ScriptHash())
}

// TestTribute_GetAssessment_NonExistent tests getting a non-existent assessment.
func TestTribute_GetAssessment_NonExistent(t *testing.T) {
	c := newTributeClient(t)

	// Non-existent assessment should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent assessment")
	}, "getAssessment", int64(999))
}

// TestTribute_GetIncentive_NonExistent tests getting a non-existent incentive.
func TestTribute_GetIncentive_NonExistent(t *testing.T) {
	c := newTributeClient(t)

	// Non-existent incentive should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent incentive")
	}, "getIncentive", int64(999))
}

// TestTribute_GetRedistribution_NonExistent tests getting a non-existent redistribution.
func TestTribute_GetRedistribution_NonExistent(t *testing.T) {
	c := newTributeClient(t)

	// Non-existent redistribution should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent redistribution")
	}, "getRedistribution", int64(999))
}

// TestTribute_CreateVelocityAccount_NoVita tests that creating account without Vita fails.
func TestTribute_CreateVelocityAccount_NoVita(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	invoker := c.WithSigners(acc)

	// Should fail - no Vita registered
	invoker.InvokeFail(t, "owner must have an active Vita", "createVelocityAccount", acc.ScriptHash())
}

// TestTribute_GrantExemption_NotAdmin tests that non-admin cannot grant exemption.
func TestTribute_GrantExemption_NotAdmin(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	invoker := c.WithSigners(acc)

	// Should fail - not admin
	invoker.InvokeFail(t, "caller is not an authorized tribute admin", "grantExemption",
		acc.ScriptHash(), "test exemption")
}

// TestTribute_RevokeExemption_NotAdmin tests that non-admin cannot revoke exemption.
func TestTribute_RevokeExemption_NotAdmin(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	invoker := c.WithSigners(acc)

	// Should fail - not admin
	invoker.InvokeFail(t, "caller is not an authorized tribute admin", "revokeExemption", acc.ScriptHash())
}

// TestTribute_GrantIncentive_NotAdmin tests that non-admin cannot grant incentive.
func TestTribute_GrantIncentive_NotAdmin(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	invoker := c.WithSigners(acc)

	// Should fail - not admin
	invoker.InvokeFail(t, "caller is not an authorized tribute admin", "grantIncentive",
		acc.ScriptHash(), int64(0), int64(1000), "test incentive")
}

// TestTribute_WaiveTribute_NotAdmin tests that non-admin cannot waive tribute.
func TestTribute_WaiveTribute_NotAdmin(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	invoker := c.WithSigners(acc)

	// Should fail - not admin
	invoker.InvokeFail(t, "caller is not an authorized tribute admin", "waiveTribute",
		int64(0), "test waive")
}

// TestTribute_Redistribute_NotCommittee tests that non-committee cannot redistribute.
func TestTribute_Redistribute_NotCommittee(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	invoker := c.WithSigners(acc)

	// Should fail - not committee
	invoker.InvokeFail(t, "invalid committee signature", "redistribute",
		"all_citizens", int64(100))
}

// TestTribute_GetPendingAssessment_NoAccount tests getting pending assessment for non-existent account.
func TestTribute_GetPendingAssessment_NoAccount(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// No account = null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent pending assessment")
	}, "getPendingAssessment", acc.ScriptHash())
}

// TestTribute_GetUnclaimedIncentives_NonExistent tests getting unclaimed incentives for non-existent account.
func TestTribute_GetUnclaimedIncentives_NonExistent(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent account should return 0
	c.Invoke(t, 0, "getUnclaimedIncentives", acc.ScriptHash())
}

// TestTribute_CreateVelocityAccountWithVita tests account creation with a valid Vita.
func TestTribute_CreateVelocityAccountWithVita(t *testing.T) {
	c := newTributeClient(t)
	e := c.Executor

	// Register Vita first
	vitaHash := e.NativeHash(t, nativenames.Vita)
	acc := e.NewAccount(t)
	vitaInvoker := e.NewInvoker(vitaHash, acc)

	owner := acc.ScriptHash()
	personHash := hash.Sha256(owner.BytesBE()).BytesBE()
	isEntity := false
	recoveryHash := hash.Sha256([]byte("recovery")).BytesBE()
	birthTimestamp := time.Now().Unix() - 30*365*24*60*60 // 30 years old

	// Register Vita token
	vitaInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		_, ok := stack[0].Value().([]byte)
		require.True(t, ok, "expected ByteArray result")
	}, "register", owner.BytesBE(), personHash, isEntity, recoveryHash, birthTimestamp)

	// Now create Tribute account
	tributeInvoker := c.WithSigners(acc)
	tributeInvoker.Invoke(t, true, "createVelocityAccount", owner.BytesBE())

	// Verify account exists
	tributeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		arr, ok := stack[0].Value().([]stackitem.Item)
		require.True(t, ok, "expected array result for existing account")
		require.GreaterOrEqual(t, len(arr), 15) // VelocityAccount has 15 fields
	}, "getAccount", owner.BytesBE())

	// Verify total accounts increased
	c.Invoke(t, 1, "getTotalAccounts")

	// Verify default velocity (5000 = 50%)
	c.Invoke(t, 5000, "getVelocity", owner.BytesBE())

	// Verify no hoarding (0 = HoardingNone)
	c.Invoke(t, 0, "getHoardingLevel", owner.BytesBE())
}
