// Copyright 2024 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package privateapi

import (
	"context"
	"io"
	"testing"

	"google.golang.org/grpc"

	"github.com/erigontech/erigon/common"
	"github.com/erigontech/erigon/node/gointerfaces"
	"github.com/erigontech/erigon/node/gointerfaces/remoteproto"
	"github.com/erigontech/erigon/node/gointerfaces/typesproto"
	"github.com/erigontech/erigon/node/shards"
)

var (
	address1   = common.HexToHash("0xdac17f958d2ee523a2206206994597c13d831ec7")
	topic1     = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
	address160 *typesproto.H160
	topic1H256 *typesproto.H256
)

func init() {
	var a common.Address
	a.SetBytes(address1.Bytes())
	address160 = gointerfaces.ConvertAddressToH160(a)
	topic1H256 = gointerfaces.ConvertHashToH256(topic1)
}

type testServer struct {
	received         chan *remoteproto.LogsFilterRequest
	receiveCompleted chan struct{}
	sent             []*remoteproto.SubscribeLogsReply
	ctx              context.Context
	grpc.ServerStream
}

func newTestServer(ctx context.Context) *testServer {
	ts := &testServer{
		received:         make(chan *remoteproto.LogsFilterRequest, 256),
		receiveCompleted: make(chan struct{}, 1),
		sent:             make([]*remoteproto.SubscribeLogsReply, 0),
		ctx:              ctx,
		ServerStream:     nil,
	}
	go func() {
		<-ts.ctx.Done()
		close(ts.received)
	}()
	return ts
}

func (ts *testServer) Send(m *remoteproto.SubscribeLogsReply) error {
	ts.sent = append(ts.sent, m)
	return nil
}

func (ts *testServer) Recv() (*remoteproto.LogsFilterRequest, error) {
	// notify receive completed when the last request has been processed
	if len(ts.received) == 0 {
		ts.receiveCompleted <- struct{}{}
	}

	request, ok := <-ts.received
	if !ok {
		return nil, io.EOF
	}
	return request, nil
}

func createLog() *remoteproto.SubscribeLogsReply {
	return &remoteproto.SubscribeLogsReply{
		Address:          gointerfaces.ConvertAddressToH160([20]byte{}),
		BlockHash:        gointerfaces.ConvertHashToH256([32]byte{}),
		BlockNumber:      0,
		Data:             []byte{},
		LogIndex:         0,
		Topics:           []*typesproto.H256{gointerfaces.ConvertHashToH256([32]byte{99, 99})},
		TransactionHash:  gointerfaces.ConvertHashToH256([32]byte{}),
		TransactionIndex: 0,
		Removed:          false,
	}
}

func TestLogsFilter_EmptyFilter_DoesNotDistributeAnything(t *testing.T) {
	events := shards.NewEvents()
	agg := NewLogsFilterAggregator(events)

	ctx := t.Context()
	srv := newTestServer(ctx)

	req1 := &remoteproto.LogsFilterRequest{
		AllAddresses: false,
		Addresses:    nil,
		AllTopics:    false,
		Topics:       nil,
	}
	srv.received <- req1

	go func() {
		err := agg.subscribeLogs(srv)
		if err != nil {
			t.Error(err)
		}
	}()

	<-srv.receiveCompleted

	// now see if a log would be sent or not
	log := createLog()
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})

	if len(srv.sent) != 0 {
		t.Error("expected the sent slice to be empty")
	}
}

func TestLogsFilter_AllAddressesAndTopicsFilter_DistributesLogRegardless(t *testing.T) {
	events := shards.NewEvents()
	agg := NewLogsFilterAggregator(events)

	ctx := t.Context()
	srv := newTestServer(ctx)

	req1 := &remoteproto.LogsFilterRequest{
		AllAddresses: true,
		Addresses:    nil,
		AllTopics:    true,
		Topics:       nil,
	}
	srv.received <- req1

	go func() {
		err := agg.subscribeLogs(srv)
		if err != nil {
			t.Error(err)
		}
	}()

	<-srv.receiveCompleted

	// now see if a log would be sent or not
	log := createLog()
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 1 {
		t.Error("expected the sent slice to have the log present")
	}

	log = createLog()
	log.Topics = []*typesproto.H256{topic1H256}
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 2 {
		t.Error("expected any topic to be allowed through the filter")
	}

	log = createLog()
	log.Address = address160
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 3 {
		t.Error("expected any address to be allowed through the filter")
	}
}

func TestLogsFilter_TopicFilter_OnlyAllowsThatTopicThrough(t *testing.T) {
	events := shards.NewEvents()
	agg := NewLogsFilterAggregator(events)

	ctx := t.Context()
	srv := newTestServer(ctx)

	req1 := &remoteproto.LogsFilterRequest{
		AllAddresses: true, // need to allow all addresses on the request else it will filter on them
		Addresses:    nil,
		AllTopics:    false,
		Topics:       []*typesproto.H256{topic1H256},
	}
	srv.received <- req1

	go func() {
		err := agg.subscribeLogs(srv)
		if err != nil {
			t.Error(err)
		}
	}()

	<-srv.receiveCompleted

	// now see if a log would be sent or not
	log := createLog()
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 0 {
		t.Error("the sent slice should be empty as the topic didn't match")
	}

	log = createLog()
	log.Topics = []*typesproto.H256{topic1H256}
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 1 {
		t.Error("expected the log to be distributed as the topic matched")
	}
}

func TestLogsFilter_AddressFilter_OnlyAllowsThatAddressThrough(t *testing.T) {
	events := shards.NewEvents()
	agg := NewLogsFilterAggregator(events)

	ctx := t.Context()
	srv := newTestServer(ctx)

	req1 := &remoteproto.LogsFilterRequest{
		AllAddresses: false,
		Addresses:    []*typesproto.H160{address160},
		AllTopics:    true,
		Topics:       []*typesproto.H256{},
	}
	srv.received <- req1

	go func() {
		err := agg.subscribeLogs(srv)
		if err != nil {
			t.Error(err)
		}
	}()

	<-srv.receiveCompleted

	// now see if a log would be sent or not
	log := createLog()
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 0 {
		t.Error("the sent slice should be empty as the address didn't match")
	}

	log = createLog()
	log.Address = address160
	_ = agg.distributeLogs([]*remoteproto.SubscribeLogsReply{log})
	if len(srv.sent) != 1 {
		t.Error("expected the log to be distributed as the address matched")
	}
}
