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 newSalusClient(t *testing.T) *tutustest.ContractInvoker {
	return newNativeClient(t, nativenames.Salus)
}

// TestSalus_GetConfig tests the getConfig method.
func TestSalus_GetConfig(t *testing.T) {
	c := newSalusClient(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), 4) // SalusConfig has 4 fields
	}, "getConfig")
}

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

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

// TestSalus_GetTotalRecords tests the getTotalRecords method.
func TestSalus_GetTotalRecords(t *testing.T) {
	c := newSalusClient(t)

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

// TestSalus_GetTotalProviders tests the getTotalProviders method.
func TestSalus_GetTotalProviders(t *testing.T) {
	c := newSalusClient(t)

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

// TestSalus_GetAccount_NonExistent tests getting a non-existent account.
func TestSalus_GetAccount_NonExistent(t *testing.T) {
	c := newSalusClient(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())
}

// TestSalus_GetCredits_NonExistent tests getting credits for non-existent account.
func TestSalus_GetCredits_NonExistent(t *testing.T) {
	c := newSalusClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

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

// TestSalus_ActivateHealthcare_NoVita tests that activating healthcare without Vita fails.
func TestSalus_ActivateHealthcare_NoVita(t *testing.T) {
	c := newSalusClient(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", "activateHealthcare", acc.ScriptHash())
}

// TestSalus_AllocateCredits_NotCommittee tests that non-committee cannot allocate credits.
func TestSalus_AllocateCredits_NotCommittee(t *testing.T) {
	c := newSalusClient(t)
	e := c.Executor

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

	// Should fail - not committee
	invoker.InvokeFail(t, "invalid committee signature", "allocateCredits",
		acc.ScriptHash(), 100, "test allocation")
}

// TestSalus_RegisterProvider_NotCommittee tests that non-committee cannot register providers.
func TestSalus_RegisterProvider_NotCommittee(t *testing.T) {
	c := newSalusClient(t)
	e := c.Executor

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

	licenseHash := make([]byte, 32) // 32-byte hash

	// Should fail - not committee
	invoker.InvokeFail(t, "invalid committee signature", "registerProvider",
		acc.ScriptHash(), "Test Hospital", "General", licenseHash)
}

// TestSalus_GetProvider_NonExistent tests getting a non-existent provider.
func TestSalus_GetProvider_NonExistent(t *testing.T) {
	c := newSalusClient(t)

	// Non-existent provider 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 provider")
	}, "getProvider", int64(999))
}

// TestSalus_GetAuthorization_NonExistent tests getting a non-existent authorization.
func TestSalus_GetAuthorization_NonExistent(t *testing.T) {
	c := newSalusClient(t)

	// Non-existent authorization 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 authorization")
	}, "getAuthorization", int64(999))
}

// TestSalus_HasAccess_NoAccount tests hasAccess for non-existent account.
func TestSalus_HasAccess_NoAccount(t *testing.T) {
	c := newSalusClient(t)
	e := c.Executor

	patient := e.NewAccount(t)
	provider := e.NewAccount(t)

	// No account = no access
	c.Invoke(t, false, "hasAccess", patient.ScriptHash(), provider.ScriptHash())
}

// TestSalus_GetEmergencyAccess_NonExistent tests getting non-existent emergency access.
func TestSalus_GetEmergencyAccess_NonExistent(t *testing.T) {
	c := newSalusClient(t)

	// Non-existent emergency access 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 emergency access")
	}, "getEmergencyAccess", int64(999))
}

// TestSalus_ActivateHealthcareWithVita tests healthcare activation with a valid Vita.
func TestSalus_ActivateHealthcareWithVita(t *testing.T) {
	c := newSalusClient(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 activate Salus account - need to pass owner as BytesBE for Hash160 type
	salusInvoker := c.WithSigners(acc)
	salusInvoker.Invoke(t, true, "activateHealthcare", owner.BytesBE())

	// Verify account exists - also pass as BytesBE
	salusInvoker.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), 10) // HealthcareAccount has 10 fields
	}, "getAccount", owner.BytesBE())

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

// TestSalus_AllocateCredits tests credit allocation by committee.
func TestSalus_AllocateCredits(t *testing.T) {
	c := newSalusClient(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 returns ByteArray (tokenID), not Null
	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)

	// Activate Salus account - use BytesBE for Hash160
	salusInvoker := c.WithSigners(acc)
	salusInvoker.Invoke(t, true, "activateHealthcare", owner.BytesBE())

	// Allocate credits as committee - use BytesBE for Hash160
	committeeInvoker := c.WithSigners(c.Committee)
	committeeInvoker.Invoke(t, true, "allocateCredits", owner.BytesBE(), int64(500), "annual allocation")

	// Default annual allocation is 10000, so after adding 500 it should be 10500
	c.Invoke(t, 10500, "getCredits", owner.BytesBE())
}

// TestSalus_RegisterProvider tests provider registration by committee.
func TestSalus_RegisterProvider(t *testing.T) {
	c := newSalusClient(t)
	e := c.Executor

	provider := e.NewAccount(t)
	licenseHash := make([]byte, 32)
	copy(licenseHash, []byte("license"))

	// Register provider as committee
	committeeInvoker := c.WithSigners(c.Committee)
	committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		// First provider gets ID 0
	}, "registerProvider", provider.ScriptHash(), "Test Hospital", "General", licenseHash)

	// Verify total providers increased
	c.Invoke(t, 1, "getTotalProviders")
}
