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

// TestPalam_GetConfig tests the getConfig method.
func TestPalam_GetConfig(t *testing.T) {
	c := newPalamClient(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) // PalamConfig has at least 4 fields
	}, "getConfig")
}

// TestPalam_GetTotalFlows tests the getTotalFlows method.
func TestPalam_GetTotalFlows(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_GetTotalAttachments tests the getTotalAttachments method.
func TestPalam_GetTotalAttachments(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_GetTotalRequests tests the getTotalRequests method.
func TestPalam_GetTotalRequests(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_GetTotalLogs tests the getTotalLogs method.
func TestPalam_GetTotalLogs(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_GetFlow_NonExistent tests getting a non-existent flow.
func TestPalam_GetFlow_NonExistent(t *testing.T) {
	c := newPalamClient(t)

	// Create a valid 32-byte flow ID (all zeros)
	flowID := make([]byte, 32)

	// Non-existent flow 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 flow")
	}, "getFlow", flowID)
}

// TestPalam_GetAttachment_NonExistent tests getting a non-existent attachment.
func TestPalam_GetAttachment_NonExistent(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_GetDeclassifyRequest_NonExistent tests getting a non-existent request.
func TestPalam_GetDeclassifyRequest_NonExistent(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_GetAccessLog_NonExistent tests getting a non-existent access log.
func TestPalam_GetAccessLog_NonExistent(t *testing.T) {
	c := newPalamClient(t)

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

// TestPalam_HasDeclassifyGrant_NoGrant tests hasDeclassifyGrant when no grant exists.
func TestPalam_HasDeclassifyGrant_NoGrant(t *testing.T) {
	c := newPalamClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	flowID := make([]byte, 32) // Non-existent flow

	// Should return false
	c.Invoke(t, false, "hasDeclassifyGrant", flowID, acc.ScriptHash())
}

// TestPalam_GetRolePermissions tests the getRolePermissions method.
func TestPalam_GetRolePermissions(t *testing.T) {
	c := newPalamClient(t)

	// Get permissions for consumer role
	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), 1) // At least some permissions
	}, "getRolePermissions", int64(state.PalamRoleConsumer))
}

// TestPalam_RecordFlow_InvalidTag tests recording flow with invalid tag.
func TestPalam_RecordFlow_InvalidTag(t *testing.T) {
	c := newPalamClient(t)

	participants := []any{}
	consumerData := []byte{}
	merchantData := []byte{}
	distributorData := []byte{}
	producerData := []byte{}
	ngoData := []byte{}
	auditorData := []byte{}
	prevFlowID := make([]byte, 32)

	// Empty tag should fail
	c.InvokeFail(t, "invalid tag", "recordFlow",
		"", 1000, "test-bucket", participants,
		consumerData, merchantData, distributorData, producerData, ngoData, auditorData, prevFlowID)
}

// TestPalam_RecordFlow_InvalidBucket tests recording flow with invalid bucket.
func TestPalam_RecordFlow_InvalidBucket(t *testing.T) {
	c := newPalamClient(t)

	participants := []any{}
	consumerData := []byte{}
	merchantData := []byte{}
	distributorData := []byte{}
	producerData := []byte{}
	ngoData := []byte{}
	auditorData := []byte{}
	prevFlowID := make([]byte, 32)

	// Empty bucket should fail
	c.InvokeFail(t, "invalid bucket", "recordFlow",
		"test-tag", 1000, "", participants,
		consumerData, merchantData, distributorData, producerData, ngoData, auditorData, prevFlowID)
}

// TestPalam_AttachToFlow_FlowNotFound tests attaching to non-existent flow.
func TestPalam_AttachToFlow_FlowNotFound(t *testing.T) {
	c := newPalamClient(t)

	flowID := make([]byte, 32) // Non-existent flow
	encryptedData := []byte("test data")

	// Should fail - flow doesn't exist
	c.InvokeFail(t, "flow not found", "attachToFlow", flowID, "receipt", encryptedData)
}

// TestPalam_RequestDeclassify_FlowNotFound tests requesting declassify for non-existent flow.
func TestPalam_RequestDeclassify_FlowNotFound(t *testing.T) {
	c := newPalamClient(t)

	flowID := make([]byte, 32) // Non-existent flow
	reason := "This is a test reason that is at least 50 characters long for validation"

	// Should fail - flow doesn't exist
	c.InvokeFail(t, "flow not found", "requestDeclassify", flowID, "CASE-123", reason, 1000)
}

// TestPalam_RequestDeclassify_InvalidCaseID tests declassify with invalid case ID.
func TestPalam_RequestDeclassify_InvalidCaseID(t *testing.T) {
	c := newPalamClient(t)

	flowID := make([]byte, 32)
	reason := "This is a test reason that is at least 50 characters long for validation"

	// Empty case ID should fail
	c.InvokeFail(t, "invalid case ID", "requestDeclassify", flowID, "", reason, 1000)
}

// TestPalam_RequestDeclassify_InvalidReason tests declassify with short reason.
func TestPalam_RequestDeclassify_InvalidReason(t *testing.T) {
	c := newPalamClient(t)

	flowID := make([]byte, 32)

	// Reason too short (< 50 chars) should fail
	c.InvokeFail(t, "invalid reason", "requestDeclassify", flowID, "CASE-123", "too short", 1000)
}

// TestPalam_ApproveDeclassify_NotJudge tests that non-judges cannot approve.
func TestPalam_ApproveDeclassify_NotJudge(t *testing.T) {
	c := newPalamClient(t)
	e := c.Executor

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

	// Should fail - caller is not a judge (role check happens before request lookup)
	invoker.InvokeFail(t, "caller is not a judge", "approveDeclassify", 999)
}

// TestPalam_DenyDeclassify_NotJudge tests that non-judges cannot deny.
func TestPalam_DenyDeclassify_NotJudge(t *testing.T) {
	c := newPalamClient(t)
	e := c.Executor

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

	// Should fail - caller is not a judge (role check happens before request lookup)
	invoker.InvokeFail(t, "caller is not a judge", "denyDeclassify", 999, "Test denial reason")
}

// Note: More complex tests that require:
// - Recording flows with Vita tokens
// - Attaching data to flows
// - Full declassification workflow (request -> approve/deny)
// - Access logging
// Would require more sophisticated test setup with role-based authorization.
