package tutustest

import (
	"testing"

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

// EventMatcher provides fluent event validation for contract tests.
type EventMatcher struct {
	t        testing.TB
	events   []state.NotificationEvent
	contract util.Uint160
}

// NewEventMatcher creates a matcher for the given events.
func NewEventMatcher(t testing.TB, events []state.NotificationEvent) *EventMatcher {
	return &EventMatcher{
		t:      t,
		events: events,
	}
}

// FromContract filters events to only those from the specified contract.
func (m *EventMatcher) FromContract(hash util.Uint160) *EventMatcher {
	m.contract = hash
	return m
}

// HasEvent checks that at least one event with the given name exists.
func (m *EventMatcher) HasEvent(name string) *EventMatcher {
	found := false
	for _, e := range m.events {
		if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
			continue
		}
		if e.Name == name {
			found = true
			break
		}
	}
	require.True(m.t, found, "expected event %q not found", name)
	return m
}

// HasNoEvent checks that no event with the given name exists.
func (m *EventMatcher) HasNoEvent(name string) *EventMatcher {
	for _, e := range m.events {
		if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
			continue
		}
		require.NotEqual(m.t, name, e.Name, "unexpected event %q found", name)
	}
	return m
}

// CountEvents returns the number of events with the given name.
func (m *EventMatcher) CountEvents(name string) int {
	count := 0
	for _, e := range m.events {
		if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
			continue
		}
		if e.Name == name {
			count++
		}
	}
	return count
}

// RequireEventCount asserts exactly N events with the given name.
func (m *EventMatcher) RequireEventCount(name string, count int) *EventMatcher {
	actual := m.CountEvents(name)
	require.Equal(m.t, count, actual, "expected %d %q events, got %d", count, name, actual)
	return m
}

// GetEvent returns the first event with the given name.
func (m *EventMatcher) GetEvent(name string) *state.NotificationEvent {
	for _, e := range m.events {
		if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
			continue
		}
		if e.Name == name {
			return &e
		}
	}
	return nil
}

// GetEvents returns all events with the given name.
func (m *EventMatcher) GetEvents(name string) []state.NotificationEvent {
	var result []state.NotificationEvent
	for _, e := range m.events {
		if m.contract != (util.Uint160{}) && e.ScriptHash != m.contract {
			continue
		}
		if e.Name == name {
			result = append(result, e)
		}
	}
	return result
}

// EventValidator provides detailed validation of a single event.
type EventValidator struct {
	t     testing.TB
	event *state.NotificationEvent
}

// ValidateEvent creates a validator for a specific event.
func (m *EventMatcher) ValidateEvent(name string) *EventValidator {
	event := m.GetEvent(name)
	require.NotNil(m.t, event, "event %q not found", name)
	return &EventValidator{
		t:     m.t,
		event: event,
	}
}

// HasArgs checks that the event has the expected number of arguments.
func (v *EventValidator) HasArgs(count int) *EventValidator {
	arr, ok := v.event.Item.Value().([]stackitem.Item)
	require.True(v.t, ok, "event item is not an array")
	require.Equal(v.t, count, len(arr), "expected %d args, got %d", count, len(arr))
	return v
}

// ArgEquals checks that argument at index equals the expected value.
func (v *EventValidator) ArgEquals(index int, expected any) *EventValidator {
	arr, ok := v.event.Item.Value().([]stackitem.Item)
	require.True(v.t, ok, "event item is not an array")
	require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)

	actual := arr[index]
	exp := stackitem.Make(expected)
	require.True(v.t, actual.Equals(exp), "arg[%d]: expected %v, got %v", index, expected, actual)
	return v
}

// ArgIsHash160 checks that argument at index is a valid Hash160.
func (v *EventValidator) ArgIsHash160(index int) *EventValidator {
	arr, ok := v.event.Item.Value().([]stackitem.Item)
	require.True(v.t, ok, "event item is not an array")
	require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)

	bs, err := arr[index].TryBytes()
	require.NoError(v.t, err, "arg[%d] is not bytes", index)
	require.Equal(v.t, 20, len(bs), "arg[%d] is not Hash160 (len=%d)", index, len(bs))
	return v
}

// ArgIsPositive checks that argument at index is a positive integer.
func (v *EventValidator) ArgIsPositive(index int) *EventValidator {
	arr, ok := v.event.Item.Value().([]stackitem.Item)
	require.True(v.t, ok, "event item is not an array")
	require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)

	n, err := arr[index].TryInteger()
	require.NoError(v.t, err, "arg[%d] is not integer", index)
	require.Greater(v.t, n.Int64(), int64(0), "arg[%d] is not positive", index)
	return v
}

// ArgIsNonNegative checks that argument at index is a non-negative integer.
func (v *EventValidator) ArgIsNonNegative(index int) *EventValidator {
	arr, ok := v.event.Item.Value().([]stackitem.Item)
	require.True(v.t, ok, "event item is not an array")
	require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)

	n, err := arr[index].TryInteger()
	require.NoError(v.t, err, "arg[%d] is not integer", index)
	require.GreaterOrEqual(v.t, n.Int64(), int64(0), "arg[%d] is negative", index)
	return v
}

// GetArg returns the argument at the given index.
func (v *EventValidator) GetArg(index int) stackitem.Item {
	arr, ok := v.event.Item.Value().([]stackitem.Item)
	require.True(v.t, ok, "event item is not an array")
	require.Greater(v.t, len(arr), index, "arg index %d out of bounds", index)
	return arr[index]
}

// GetArgBytes returns the argument at the given index as bytes.
func (v *EventValidator) GetArgBytes(index int) []byte {
	item := v.GetArg(index)
	bs, err := item.TryBytes()
	require.NoError(v.t, err)
	return bs
}

// GetArgInt returns the argument at the given index as int64.
func (v *EventValidator) GetArgInt(index int) int64 {
	item := v.GetArg(index)
	n, err := item.TryInteger()
	require.NoError(v.t, err)
	return n.Int64()
}

// GetArgHash160 returns the argument at the given index as Hash160.
func (v *EventValidator) GetArgHash160(index int) util.Uint160 {
	bs := v.GetArgBytes(index)
	require.Equal(v.t, 20, len(bs))
	return util.Uint160(bs)
}

// Common Tutus event names for convenience
const (
	// Vita events
	EventVitaRegistered = "VitaRegistered"
	EventVitaSuspended  = "VitaSuspended"
	EventVitaRevoked    = "VitaRevoked"

	// VTS events
	EventTransfer = "Transfer"
	EventMint     = "Mint"
	EventBurn     = "Burn"

	// Eligere events
	EventProposalCreated = "ProposalCreated"
	EventVoteCast        = "VoteCast"
	EventProposalPassed  = "ProposalPassed"
	EventProposalFailed  = "ProposalFailed"

	// Lex events
	EventLawEnacted    = "LawEnacted"
	EventLawRepealed   = "LawRepealed"
	EventRightRestricted = "RightRestricted"
	EventRightRestored   = "RightRestored"

	// RoleRegistry events
	EventRoleGranted = "RoleGranted"
	EventRoleRevoked = "RoleRevoked"

	// Scire events
	EventEnrollment     = "Enrollment"
	EventCertification  = "Certification"

	// Salus events
	EventMedicalRecord    = "MedicalRecord"
	EventEmergencyAccess  = "EmergencyAccess"

	// Federation events
	EventVisitorRegistered = "VisitorRegistered"
	EventAsylumGranted     = "AsylumGranted"
	EventCitizenNaturalized = "CitizenNaturalized"
)

// TransferEventValidator is a specialized validator for Transfer events.
type TransferEventValidator struct {
	*EventValidator
}

// ValidateTransfer creates a validator specifically for Transfer events.
func (m *EventMatcher) ValidateTransfer() *TransferEventValidator {
	return &TransferEventValidator{
		EventValidator: m.ValidateEvent(EventTransfer),
	}
}

// From checks the sender of the transfer.
func (v *TransferEventValidator) From(expected util.Uint160) *TransferEventValidator {
	v.ArgEquals(0, expected.BytesBE())
	return v
}

// To checks the recipient of the transfer.
func (v *TransferEventValidator) To(expected util.Uint160) *TransferEventValidator {
	v.ArgEquals(1, expected.BytesBE())
	return v
}

// Amount checks the transfer amount.
func (v *TransferEventValidator) Amount(expected int64) *TransferEventValidator {
	v.ArgEquals(2, expected)
	return v
}

// IsMint checks that this is a mint (from is null).
func (v *TransferEventValidator) IsMint() *TransferEventValidator {
	arr := v.event.Item.Value().([]stackitem.Item)
	require.Equal(v.t, stackitem.AnyT, arr[0].Type(), "expected null sender for mint")
	return v
}

// IsBurn checks that this is a burn (to is null).
func (v *TransferEventValidator) IsBurn() *TransferEventValidator {
	arr := v.event.Item.Value().([]stackitem.Item)
	require.Equal(v.t, stackitem.AnyT, arr[1].Type(), "expected null recipient for burn")
	return v
}
