package rpcevent

import (
	"testing"

	"git.marketally.com/tutus-one/tutus-chain/pkg/core/block"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/mempoolevent"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/state"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/transaction"
	"git.marketally.com/tutus-one/tutus-chain/pkg/tutusrpc"
	"git.marketally.com/tutus-one/tutus-chain/pkg/tutusrpc/result"
	"git.marketally.com/tutus-one/tutus-chain/pkg/network/payload"
	"git.marketally.com/tutus-one/tutus-chain/pkg/smartcontract"
	"git.marketally.com/tutus-one/tutus-chain/pkg/util"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/vmstate"
	"github.com/stretchr/testify/require"
)

type (
	testComparator struct {
		id     tutusrpc.EventID
		filter tutusrpc.SubscriptionFilter
	}
	testContainer struct {
		id  tutusrpc.EventID
		pld any
	}
)

func (c testComparator) EventID() tutusrpc.EventID {
	return c.id
}
func (c testComparator) Filter() tutusrpc.SubscriptionFilter {
	return c.filter
}
func (c testContainer) EventID() tutusrpc.EventID {
	return c.id
}
func (c testContainer) EventPayload() any {
	return c.pld
}

func TestMatches(t *testing.T) {
	primary := byte(1)
	badPrimary := byte(2)
	index := uint32(5)
	badHigherIndex := uint32(6)
	badLowerIndex := index - 1
	sender := util.Uint160{1, 2, 3}
	signer := util.Uint160{4, 5, 6}
	contract := util.Uint160{7, 8, 9}
	badUint160 := util.Uint160{9, 9, 9}
	cnt := util.Uint256{1, 2, 3}
	badUint256 := util.Uint256{9, 9, 9}
	name := "ntf name"
	badName := "bad name"
	goodType := mempoolevent.TransactionAdded
	badType := mempoolevent.TransactionRemoved
	parameters, err := smartcontract.NewParametersFromValues(1, "2", []byte{3})
	require.NoError(t, err)
	badParameters, err := smartcontract.NewParametersFromValues([]byte{3}, "2", []byte{1})
	require.NoError(t, err)
	bContainer := testContainer{
		id: tutusrpc.BlockEventID,
		pld: &block.Block{
			Header: block.Header{PrimaryIndex: byte(primary), Index: index},
		},
	}
	headerContainer := testContainer{
		id:  tutusrpc.HeaderOfAddedBlockEventID,
		pld: &block.Header{PrimaryIndex: byte(primary), Index: index},
	}
	st := vmstate.Halt
	goodState := st.String()
	badState := "FAULT"
	txContainer := testContainer{
		id:  tutusrpc.TransactionEventID,
		pld: &transaction.Transaction{Signers: []transaction.Signer{{Account: sender}, {Account: signer}}},
	}
	ntfContainer := testContainer{
		id:  tutusrpc.NotificationEventID,
		pld: &state.ContainedNotificationEvent{NotificationEvent: state.NotificationEvent{ScriptHash: contract, Name: name}},
	}
	ntfContainerParameters := testContainer{
		id: tutusrpc.NotificationEventID,
		pld: &state.ContainedNotificationEvent{
			NotificationEvent: state.NotificationEvent{
				ScriptHash: contract,
				Name:       name,
				Item:       stackitem.NewArray(prmsToStack(t, parameters)),
			},
		},
	}
	exContainer := testContainer{
		id:  tutusrpc.ExecutionEventID,
		pld: &state.AppExecResult{Container: cnt, Execution: state.Execution{VMState: st}},
	}
	ntrContainer := testContainer{
		id: tutusrpc.NotaryRequestEventID,
		pld: &result.NotaryRequestEvent{
			Type: goodType,
			NotaryRequest: &payload.P2PNotaryRequest{
				MainTransaction:     &transaction.Transaction{Signers: []transaction.Signer{{Account: signer}}},
				FallbackTransaction: &transaction.Transaction{Signers: []transaction.Signer{{Account: util.Uint160{}}, {Account: sender}}},
			},
		},
	}
	mempoolContainer := testContainer{
		id: tutusrpc.MempoolEventID,
		pld: &result.MempoolEvent{
			Type: goodType,
			Tx:   &transaction.Transaction{Signers: []transaction.Signer{{Account: sender}, {Account: signer}}},
		},
	}
	missedContainer := testContainer{
		id: tutusrpc.MissedEventID,
	}
	var testCases = []struct {
		name       string
		comparator testComparator
		container  testContainer
		expected   bool
	}{
		{
			name:       "ID mismatch",
			comparator: testComparator{id: tutusrpc.TransactionEventID},
			container:  bContainer,
			expected:   false,
		},
		{
			name:       "missed event",
			comparator: testComparator{id: tutusrpc.BlockEventID},
			container:  missedContainer,
			expected:   false,
		},
		{
			name:       "block, no filter",
			comparator: testComparator{id: tutusrpc.BlockEventID},
			container:  bContainer,
			expected:   true,
		},
		{
			name: "block, primary mismatch",
			comparator: testComparator{
				id:     tutusrpc.BlockEventID,
				filter: tutusrpc.BlockFilter{Primary: &badPrimary},
			},
			container: bContainer,
			expected:  false,
		},
		{
			name: "block, since mismatch",
			comparator: testComparator{
				id:     tutusrpc.BlockEventID,
				filter: tutusrpc.BlockFilter{Since: &badHigherIndex},
			},
			container: bContainer,
			expected:  false,
		},
		{
			name: "block, till mismatch",
			comparator: testComparator{
				id:     tutusrpc.BlockEventID,
				filter: tutusrpc.BlockFilter{Till: &badLowerIndex},
			},
			container: bContainer,
			expected:  false,
		},
		{
			name: "block, filter match",
			comparator: testComparator{
				id:     tutusrpc.BlockEventID,
				filter: tutusrpc.BlockFilter{Primary: &primary, Since: &index, Till: &index},
			},
			container: bContainer,
			expected:  true,
		},
		{
			name:       "header, no filter",
			comparator: testComparator{id: tutusrpc.HeaderOfAddedBlockEventID},
			container:  headerContainer,
			expected:   true,
		},
		{
			name: "header, primary mismatch",
			comparator: testComparator{
				id:     tutusrpc.HeaderOfAddedBlockEventID,
				filter: tutusrpc.BlockFilter{Primary: &badPrimary},
			},
			container: headerContainer,
			expected:  false,
		},
		{
			name: "header, since mismatch",
			comparator: testComparator{
				id:     tutusrpc.HeaderOfAddedBlockEventID,
				filter: tutusrpc.BlockFilter{Since: &badHigherIndex},
			},
			container: headerContainer,
			expected:  false,
		},
		{
			name: "header, till mismatch",
			comparator: testComparator{
				id:     tutusrpc.HeaderOfAddedBlockEventID,
				filter: tutusrpc.BlockFilter{Till: &badLowerIndex},
			},
			container: headerContainer,
			expected:  false,
		},
		{
			name: "header, filter match",
			comparator: testComparator{
				id:     tutusrpc.HeaderOfAddedBlockEventID,
				filter: tutusrpc.BlockFilter{Primary: &primary, Since: &index, Till: &index},
			},
			container: headerContainer,
			expected:  true,
		},
		{
			name:       "transaction, no filter",
			comparator: testComparator{id: tutusrpc.TransactionEventID},
			container:  txContainer,
			expected:   true,
		},
		{
			name: "transaction, sender mismatch",
			comparator: testComparator{
				id:     tutusrpc.TransactionEventID,
				filter: tutusrpc.TxFilter{Sender: &badUint160},
			},
			container: txContainer,
			expected:  false,
		},
		{
			name: "transaction, signer mismatch",
			comparator: testComparator{
				id:     tutusrpc.TransactionEventID,
				filter: tutusrpc.TxFilter{Signer: &badUint160},
			},
			container: txContainer,
			expected:  false,
		},
		{
			name: "transaction, filter match",
			comparator: testComparator{
				id:     tutusrpc.TransactionEventID,
				filter: tutusrpc.TxFilter{Sender: &sender, Signer: &signer},
			},
			container: txContainer,
			expected:  true,
		},
		{
			name:       "notification, no filter",
			comparator: testComparator{id: tutusrpc.NotificationEventID},
			container:  ntfContainer,
			expected:   true,
		},
		{
			name: "notification, contract mismatch",
			comparator: testComparator{
				id:     tutusrpc.NotificationEventID,
				filter: tutusrpc.NotificationFilter{Contract: &badUint160},
			},
			container: ntfContainer,
			expected:  false,
		},
		{
			name: "notification, name mismatch",
			comparator: testComparator{
				id:     tutusrpc.NotificationEventID,
				filter: tutusrpc.NotificationFilter{Name: &badName},
			},
			container: ntfContainer,
			expected:  false,
		},
		{
			name: "notification, filter match",
			comparator: testComparator{
				id:     tutusrpc.NotificationEventID,
				filter: tutusrpc.NotificationFilter{Name: &name, Contract: &contract},
			},
			container: ntfContainer,
			expected:  true,
		},
		{
			name: "notification, parameters match",
			comparator: testComparator{
				id:     tutusrpc.NotificationEventID,
				filter: tutusrpc.NotificationFilter{Name: &name, Contract: &contract, Parameters: parameters},
			},
			container: ntfContainerParameters,
			expected:  true,
		},
		{
			name: "notification, parameters mismatch",
			comparator: testComparator{
				id:     tutusrpc.NotificationEventID,
				filter: tutusrpc.NotificationFilter{Name: &name, Contract: &contract, Parameters: badParameters},
			},
			container: ntfContainerParameters,
			expected:  false,
		},
		{
			name:       "execution, no filter",
			comparator: testComparator{id: tutusrpc.ExecutionEventID},
			container:  exContainer,
			expected:   true,
		},
		{
			name: "execution, state mismatch",
			comparator: testComparator{
				id:     tutusrpc.ExecutionEventID,
				filter: tutusrpc.ExecutionFilter{State: &badState},
			},
			container: exContainer,
			expected:  false,
		},
		{
			name: "execution, container mismatch",
			comparator: testComparator{
				id:     tutusrpc.ExecutionEventID,
				filter: tutusrpc.ExecutionFilter{Container: &badUint256},
			},
			container: exContainer,
			expected:  false,
		},
		{
			name: "execution, filter mismatch",
			comparator: testComparator{
				id:     tutusrpc.ExecutionEventID,
				filter: tutusrpc.ExecutionFilter{State: &goodState, Container: &cnt},
			},
			container: exContainer,
			expected:  true,
		},
		{
			name:       "notary request, no filter",
			comparator: testComparator{id: tutusrpc.NotaryRequestEventID},
			container:  ntrContainer,
			expected:   true,
		},
		{
			name: "notary request, sender mismatch",
			comparator: testComparator{
				id:     tutusrpc.NotaryRequestEventID,
				filter: tutusrpc.NotaryRequestFilter{Sender: &badUint160},
			},
			container: ntrContainer,
			expected:  false,
		},
		{
			name: "notary request, signer mismatch",
			comparator: testComparator{
				id:     tutusrpc.NotaryRequestEventID,
				filter: tutusrpc.NotaryRequestFilter{Signer: &badUint160},
			},
			container: ntrContainer,
			expected:  false,
		},
		{
			name: "notary request, type mismatch",
			comparator: testComparator{
				id:     tutusrpc.NotaryRequestEventID,
				filter: tutusrpc.NotaryRequestFilter{Type: &badType},
			},
			container: ntrContainer,
			expected:  false,
		},
		{
			name: "notary request, filter match",
			comparator: testComparator{
				id:     tutusrpc.NotaryRequestEventID,
				filter: tutusrpc.NotaryRequestFilter{Sender: &sender, Signer: &signer, Type: &goodType},
			},
			container: ntrContainer,
			expected:  true,
		},
		{
			name:       "mempool event, no filter",
			comparator: testComparator{id: tutusrpc.MempoolEventID},
			container:  mempoolContainer,
			expected:   true,
		},
		{
			name: "mempool event, sender mismatch",
			comparator: testComparator{
				id:     tutusrpc.MempoolEventID,
				filter: tutusrpc.MempoolEventFilter{Sender: &badUint160},
			},
			container: mempoolContainer,
			expected:  false,
		},
		{
			name: "mempool event, signer mismatch",
			comparator: testComparator{
				id:     tutusrpc.MempoolEventID,
				filter: tutusrpc.MempoolEventFilter{Signer: &badUint160},
			},
			container: mempoolContainer,
			expected:  false,
		},
		{
			name: "mempool event, type mismatch",
			comparator: testComparator{
				id:     tutusrpc.MempoolEventID,
				filter: tutusrpc.MempoolEventFilter{Type: &badType},
			},
			container: mempoolContainer,
			expected:  false,
		},
		{
			name: "mempool event, filter match",
			comparator: testComparator{
				id:     tutusrpc.MempoolEventID,
				filter: tutusrpc.MempoolEventFilter{Sender: &sender, Signer: &signer, Type: &goodType},
			},
			container: mempoolContainer,
			expected:  true,
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			require.Equal(t, tc.expected, Matches(tc.comparator, tc.container))
		})
	}
}

func prmsToStack(t *testing.T, pp []smartcontract.Parameter) []stackitem.Item {
	res := make([]stackitem.Item, 0, len(pp))
	for _, p := range pp {
		s, err := p.ToStackItem()
		require.NoError(t, err)
		res = append(res, s)
	}
	return res
}
