package native_test

import (
	"testing"

	"github.com/stretchr/testify/require"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/state"
	"git.marketally.com/tutus-one/tutus-chain/pkg/tutustest"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

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

// TestCollocatio_GetConfig tests the getConfig method.
func TestCollocatio_GetConfig(t *testing.T) {
	c := newCollocatioClient(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), 13) // CollocatioConfig has 13 fields
	}, "getConfig")
}

// TestCollocatio_GetOpportunityCount tests the getOpportunityCount method.
func TestCollocatio_GetOpportunityCount(t *testing.T) {
	c := newCollocatioClient(t)

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

// TestCollocatio_GetInvestmentCount tests the getInvestmentCount method.
func TestCollocatio_GetInvestmentCount(t *testing.T) {
	c := newCollocatioClient(t)

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

// TestCollocatio_GetOpportunity_NonExistent tests getting a non-existent opportunity.
func TestCollocatio_GetOpportunity_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Non-existent opportunity 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 opportunity")
	}, "getOpportunity", 999)
}

// TestCollocatio_GetInvestment_NonExistent tests getting a non-existent investment.
func TestCollocatio_GetInvestment_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Non-existent investment 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 investment")
	}, "getInvestment", 999)
}

// TestCollocatio_GetEligibility_NonExistent tests getting eligibility for non-existent investor.
func TestCollocatio_GetEligibility_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent eligibility 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 eligibility")
	}, "getEligibility", acc.ScriptHash())
}

// TestCollocatio_IsEligible_NoVita tests eligibility check without Vita.
func TestCollocatio_IsEligible_NoVita(t *testing.T) {
	c := newCollocatioClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Should return false for all opportunity types without Vita
	c.Invoke(t, false, "isEligible", acc.ScriptHash(), int64(state.OpportunityPIO))
	c.Invoke(t, false, "isEligible", acc.ScriptHash(), int64(state.OpportunityEIO))
	c.Invoke(t, false, "isEligible", acc.ScriptHash(), int64(state.OpportunityCIO))
}

// TestCollocatio_GetViolation_NonExistent tests getting a non-existent violation.
func TestCollocatio_GetViolation_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Non-existent violation 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 violation")
	}, "getViolation", 999)
}

// TestCollocatio_GetInvestmentsByOpportunity_Empty tests getting investments for non-existent opportunity.
func TestCollocatio_GetInvestmentsByOpportunity_Empty(t *testing.T) {
	c := newCollocatioClient(t)

	// Should return empty array
	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.Equal(t, 0, len(arr), "expected empty array")
	}, "getInvestmentsByOpportunity", 999)
}

// TestCollocatio_GetOpportunitiesByType_Empty tests getting opportunities by type when none exist.
func TestCollocatio_GetOpportunitiesByType_Empty(t *testing.T) {
	c := newCollocatioClient(t)

	// Should return empty array for each type
	for _, oppType := range []state.OpportunityType{state.OpportunityPIO, state.OpportunityEIO, state.OpportunityCIO} {
		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.Equal(t, 0, len(arr), "expected empty array")
		}, "getOpportunitiesByType", int64(oppType))
	}
}

// TestCollocatio_GetOpportunitiesByStatus_Empty tests getting opportunities by status when none exist.
func TestCollocatio_GetOpportunitiesByStatus_Empty(t *testing.T) {
	c := newCollocatioClient(t)

	// Should return empty array for each status
	for _, status := range []state.OpportunityStatus{
		state.OpportunityDraft,
		state.OpportunityVoting,
		state.OpportunityActive,
		state.OpportunityClosed,
		state.OpportunityCompleted,
		state.OpportunityCancelled,
		state.OpportunityFailed,
	} {
		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.Equal(t, 0, len(arr), "expected empty array")
		}, "getOpportunitiesByStatus", int64(status))
	}
}

// TestCollocatio_ActivateOpportunity_NonExistent tests activating non-existent opportunity.
func TestCollocatio_ActivateOpportunity_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Should fail - opportunity doesn't exist
	c.InvokeFail(t, "opportunity not found", "activateOpportunity", 999)
}

// TestCollocatio_DistributeReturns_NonExistent tests distributing returns for non-existent opportunity.
func TestCollocatio_DistributeReturns_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Should fail - opportunity doesn't exist
	c.InvokeFail(t, "opportunity not found", "distributeReturns", 999, 1000_00000000)
}

// TestCollocatio_CancelOpportunity_NonExistent tests canceling non-existent opportunity.
func TestCollocatio_CancelOpportunity_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Should fail - opportunity doesn't exist
	c.InvokeFail(t, "opportunity not found", "cancelOpportunity", 999)
}

// TestCollocatio_ResolveViolation_NonExistent tests resolving non-existent violation.
func TestCollocatio_ResolveViolation_NonExistent(t *testing.T) {
	c := newCollocatioClient(t)

	// Should fail - violation doesn't exist
	c.InvokeFail(t, "violation not found", "resolveViolation", 999, "Resolved after investigation")
}

// Note: Tests that require a Vita token (like createOpportunity, invest, verifyEmployment, etc.)
// are more complex because they require a deployed helper contract since the native contracts
// use GetCallingScriptHash() for authorization, which returns the transaction script hash for
// direct calls rather than the signer's account. These would require cross-contract integration tests.
