package blossomsub

import (
	"bytes"
	"context"
	"testing"
	"time"

	pb "source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"

	"github.com/libp2p/go-libp2p/core/peer"
)

func TestBasicSubscriptionFilter(t *testing.T) {
	peerA := peer.ID("A")

	bitmask1 := []byte{0xff, 0x00, 0x00, 0x00}
	bitmask2 := []byte{0x00, 0xff, 0x00, 0x00}
	bitmask3 := []byte{0x00, 0x00, 0xff, 0x00}
	yes := true
	subs := []*pb.RPC_SubOpts{
		&pb.RPC_SubOpts{
			Bitmask:   bitmask1,
			Subscribe: yes,
		},
		&pb.RPC_SubOpts{
			Bitmask:   bitmask2,
			Subscribe: yes,
		},
		&pb.RPC_SubOpts{
			Bitmask:   bitmask3,
			Subscribe: yes,
		},
	}

	filter := NewAllowlistSubscriptionFilter(bitmask1, bitmask2)
	canSubscribe := filter.CanSubscribe(bitmask1)
	if !canSubscribe {
		t.Fatal("expected allowed subscription")
	}
	canSubscribe = filter.CanSubscribe(bitmask2)
	if !canSubscribe {
		t.Fatal("expected allowed subscription")
	}
	canSubscribe = filter.CanSubscribe(bitmask3)
	if canSubscribe {
		t.Fatal("expected disallowed subscription")
	}
	allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs)
	if err != nil {
		t.Fatal(err)
	}
	if len(allowedSubs) != 2 {
		t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs))
	}
	for _, sub := range allowedSubs {
		if bytes.Equal(sub.GetBitmask(), bitmask3) {
			t.Fatal("unpexted subscription to test3")
		}
	}

	limitFilter := WrapLimitSubscriptionFilter(filter, 2)
	_, err = limitFilter.FilterIncomingSubscriptions(peerA, subs)
	if err != ErrTooManySubscriptions {
		t.Fatal("expected rejection because of too many subscriptions")
	}
}

func TestSubscriptionFilterDeduplication(t *testing.T) {
	peerA := peer.ID("A")

	bitmask1 := []byte{0xff, 0x00, 0x00, 0x00}
	bitmask2 := []byte{0x00, 0xff, 0x00, 0x00}
	bitmask3 := []byte{0x00, 0x00, 0xff, 0x00}
	yes := true
	no := false
	subs := []*pb.RPC_SubOpts{
		&pb.RPC_SubOpts{
			Bitmask:   bitmask1,
			Subscribe: yes,
		},
		&pb.RPC_SubOpts{
			Bitmask:   bitmask1,
			Subscribe: yes,
		},

		&pb.RPC_SubOpts{
			Bitmask:   bitmask2,
			Subscribe: yes,
		},
		&pb.RPC_SubOpts{
			Bitmask:   bitmask2,
			Subscribe: no,
		},
		&pb.RPC_SubOpts{
			Bitmask:   bitmask3,
			Subscribe: yes,
		},
	}

	filter := NewAllowlistSubscriptionFilter(bitmask1, bitmask2)
	allowedSubs, err := filter.FilterIncomingSubscriptions(peerA, subs)
	if err != nil {
		t.Fatal(err)
	}
	if len(allowedSubs) != 1 {
		t.Fatalf("expected 2 allowed subscriptions but got %d", len(allowedSubs))
	}
	for _, sub := range allowedSubs {
		if bytes.Equal(sub.GetBitmask(), bitmask3) || bytes.Equal(sub.GetBitmask(), bitmask2) {
			t.Fatal("unexpected subscription")
		}
	}
}

func TestSubscriptionFilterRPC(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	hosts := getNetHosts(t, ctx, 2)
	ps1 := getPubsub(ctx, hosts[0], WithSubscriptionFilter(NewAllowlistSubscriptionFilter([]byte{0xff, 0x00, 0x00, 0x00}, []byte{0x00, 0xff, 0x00, 0x00})))
	ps2 := getPubsub(ctx, hosts[1], WithSubscriptionFilter(NewAllowlistSubscriptionFilter([]byte{0x00, 0xff, 0x00, 0x00}, []byte{0x00, 0x00, 0xff, 0x00})))

	_ = mustSubscribe(t, ps1, []byte{0xff, 0x00, 0x00, 0x00})
	_ = mustSubscribe(t, ps1, []byte{0x00, 0xff, 0x00, 0x00})
	_ = mustSubscribe(t, ps2, []byte{0x00, 0xff, 0x00, 0x00})
	_ = mustSubscribe(t, ps2, []byte{0x00, 0x00, 0xff, 0x00})

	// check the rejection as well
	_, err := ps1.Join([]byte{0x00, 0x00, 0xff, 0x00})
	if err == nil {
		t.Fatal("expected subscription error")
	}

	connect(t, hosts[0], hosts[1])

	time.Sleep(time.Second)

	var sub1, sub2, sub3 bool
	ready := make(chan struct{})

	ps1.eval <- func() {
		_, sub1 = ps1.bitmasks[string([]byte{0xff, 0x00, 0x00, 0x00})][hosts[1].ID()]
		_, sub2 = ps1.bitmasks[string([]byte{0x00, 0xff, 0x00, 0x00})][hosts[1].ID()]
		_, sub3 = ps1.bitmasks[string([]byte{0x00, 0x00, 0xff, 0x00})][hosts[1].ID()]
		ready <- struct{}{}
	}
	<-ready

	if sub1 {
		t.Fatal("expected no subscription for test1")
	}
	if !sub2 {
		t.Fatal("expected subscription for test2")
	}
	if sub3 {
		t.Fatal("expected no subscription for test1")
	}

	ps2.eval <- func() {
		_, sub1 = ps2.bitmasks[string([]byte{0xff, 0x00, 0x00, 0x00})][hosts[0].ID()]
		_, sub2 = ps2.bitmasks[string([]byte{0x00, 0xff, 0x00, 0x00})][hosts[0].ID()]
		_, sub3 = ps2.bitmasks[string([]byte{0x00, 0x00, 0xff, 0x00})][hosts[0].ID()]
		ready <- struct{}{}
	}
	<-ready

	if sub1 {
		t.Fatal("expected no subscription for test1")
	}
	if !sub2 {
		t.Fatal("expected subscription for test1")
	}
	if sub3 {
		t.Fatal("expected no subscription for test1")
	}
}
