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

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

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

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

// TestScire_GetTotalCertifications tests the getTotalCertifications method.
func TestScire_GetTotalCertifications(t *testing.T) {
	c := newScireClient(t)

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

// TestScire_GetTotalEnrollments tests the getTotalEnrollments method.
func TestScire_GetTotalEnrollments(t *testing.T) {
	c := newScireClient(t)

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

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

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

	acc := e.NewAccount(t)

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

// TestScire_CreateAccount_NoVita tests that creating account without Vita fails.
func TestScire_CreateAccount_NoVita(t *testing.T) {
	c := newScireClient(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", "createAccount", acc.ScriptHash())
}

// TestScire_AllocateCredits_NotCommittee tests that non-committee cannot allocate credits.
func TestScire_AllocateCredits_NotCommittee(t *testing.T) {
	c := newScireClient(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")
}

// TestScire_IssueCertification_NotEducator tests that non-educators cannot issue certifications.
func TestScire_IssueCertification_NotEducator(t *testing.T) {
	c := newScireClient(t)
	e := c.Executor

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

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

	// Should fail - not an educator
	invoker.InvokeFail(t, "caller is not an authorized educator", "issueCertification",
		acc.ScriptHash(), "programming", "Go Developer", contentHash, int64(0))
}

// TestScire_VerifyCertification_NonExistent tests verifying a non-existent certification.
func TestScire_VerifyCertification_NonExistent(t *testing.T) {
	c := newScireClient(t)

	// Non-existent certification should return false
	c.Invoke(t, false, "verifyCertification", int64(999))
}

// TestScire_HasCertification_NoAccount tests hasCertification for non-existent account.
func TestScire_HasCertification_NoAccount(t *testing.T) {
	c := newScireClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// No account = no certification
	c.Invoke(t, false, "hasCertification", acc.ScriptHash(), "programming")
}

// TestScire_GetEnrollment_NonExistent tests getting a non-existent enrollment.
func TestScire_GetEnrollment_NonExistent(t *testing.T) {
	c := newScireClient(t)

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

// TestScire_CreateAccountWithVita tests account creation with a valid Vita.
func TestScire_CreateAccountWithVita(t *testing.T) {
	c := newScireClient(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 Scire account - need to pass owner as BytesBE for Hash160 type
	scireInvoker := c.WithSigners(acc)
	scireInvoker.Invoke(t, true, "createAccount", owner.BytesBE())

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

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

// TestScire_AllocateCredits tests credit allocation by committee.
func TestScire_AllocateCredits(t *testing.T) {
	c := newScireClient(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)

	// Create Scire account - use BytesBE for Hash160
	scireInvoker := c.WithSigners(acc)
	scireInvoker.Invoke(t, true, "createAccount", 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")

	// Verify credits - use BytesBE for Hash160
	c.Invoke(t, 500, "getCredits", owner.BytesBE())
}

// Note: Full cross-contract testing of enrollment and certification with actual Vita
// holders and educators would require deploying helper contracts. The Scire contract
// uses GetCallingScriptHash() for authorization which returns the transaction script
// hash for direct calls. This is the intended design for cross-contract authorization.
