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

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

// TestOpus_GetTotalOperators tests the getTotalOperators method.
func TestOpus_GetTotalOperators(t *testing.T) {
	c := newOpusClient(t)

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

// TestOpus_GetTotalWorkers tests the getTotalWorkers method.
func TestOpus_GetTotalWorkers(t *testing.T) {
	c := newOpusClient(t)

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

// TestOpus_GetTotalTasks tests the getTotalTasks method.
func TestOpus_GetTotalTasks(t *testing.T) {
	c := newOpusClient(t)

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

// TestOpus_GetTotalCapabilities tests the getTotalCapabilities method.
func TestOpus_GetTotalCapabilities(t *testing.T) {
	c := newOpusClient(t)

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

// TestOpus_GetOperator_NonExistent tests getting a non-existent operator.
func TestOpus_GetOperator_NonExistent(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

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

// TestOpus_GetWorker_NonExistent tests getting a non-existent worker.
func TestOpus_GetWorker_NonExistent(t *testing.T) {
	c := newOpusClient(t)

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

// TestOpus_GetTask_NonExistent tests getting a non-existent task.
func TestOpus_GetTask_NonExistent(t *testing.T) {
	c := newOpusClient(t)

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

// TestOpus_GetCapability_NonExistent tests getting a non-existent capability.
func TestOpus_GetCapability_NonExistent(t *testing.T) {
	c := newOpusClient(t)

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

// Note: isWorkerActive and isOperatorVerified methods are not exposed as separate methods.
// Worker/operator status can be checked via getWorker/getOperator.

// TestOpus_GetWorkerCountByOperator_NonExistent tests getting worker count for non-existent operator.
func TestOpus_GetWorkerCountByOperator_NonExistent(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

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

// TestOpus_RegisterOperator_NoVita tests that registering operator without Vita fails.
func TestOpus_RegisterOperator_NoVita(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

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

// TestOpus_VerifyOperator_NotSupervisor tests that non-supervisor cannot verify operator.
func TestOpus_VerifyOperator_NotSupervisor(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

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

// Note: suspendOperator method is not currently implemented. Operators can be managed
// through their linked Vita status.

// TestOpus_RegisterWorker_NoOperator tests that registering worker without operator fails.
func TestOpus_RegisterWorker_NoOperator(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

	modelHash := hash.Sha256([]byte("model")).BytesBE()

	// Should fail - no operator registered (registerWorker takes: name, description, workerType, modelHash)
	invoker.InvokeFail(t, "operator profile not found", "registerWorker",
		"TestWorker", "Test AI Worker", int64(0), modelHash)
}

// TestOpus_SuspendWorker_NotSupervisor tests that non-supervisor cannot suspend worker.
func TestOpus_SuspendWorker_NotSupervisor(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

	// Should fail - not supervisor (checked before worker existence)
	invoker.InvokeFail(t, "caller is not an authorized supervisor", "suspendWorker", int64(999), "suspension reason")
}

// TestOpus_AssignTask_WorkerNotFound tests that assigning task to non-existent worker fails.
func TestOpus_AssignTask_WorkerNotFound(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

	// Should fail - worker not found
	invoker.InvokeFail(t, "worker not found", "assignTask",
		int64(999), "task-123", "Test Task", int64(1000))
}

// TestOpus_CompleteTask_TaskNotFound tests that completing non-existent task fails.
func TestOpus_CompleteTask_TaskNotFound(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

	resultHash := hash.Sha256([]byte("result")).BytesBE()

	// Should fail - task not found
	invoker.InvokeFail(t, "task not found", "completeTask", int64(999), resultHash, int64(500))
}

// TestOpus_AddCapability_WorkerNotFound tests that adding capability to non-existent worker fails.
func TestOpus_AddCapability_WorkerNotFound(t *testing.T) {
	c := newOpusClient(t)
	e := c.Executor

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

	// Should fail - worker not found (before checking supervisor)
	invoker.InvokeFail(t, "AI worker not found", "addCapability",
		int64(999), "language_model", int64(3), "Test certification")
}

// TestOpus_RegisterOperatorWithVita tests operator registration with a valid Vita.
func TestOpus_RegisterOperatorWithVita(t *testing.T) {
	c := newOpusClient(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 register operator (only takes owner as parameter)
	opusInvoker := c.WithSigners(acc)
	opusInvoker.Invoke(t, true, "registerOperator", owner.BytesBE())

	// Verify operator exists
	opusInvoker.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 operator")
		require.GreaterOrEqual(t, len(arr), 9) // OperatorProfile has 9 fields
	}, "getOperator", owner.BytesBE())

	// Verify total operators increased
	c.Invoke(t, 1, "getTotalOperators")
}
