package throttler

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/vishvananda/netlink"

	"github.com/tidbcloud/shared-provider/pkg/types"
)

func TestNewThrottleConfig(t *testing.T) {
	cfg, err := NewThrottleConfig(nil)
	assert.NoError(t, err)
	assert.Nil(t, cfg.Ingress)
	assert.Nil(t, cfg.Egress)

	anno := map[string]string{
		types.AnnoKeyIngress:     "2mib/s",
		types.AnnoKeyIngressPort: "123",
		types.AnnoKeyEgress:      "3kib/s",
		types.AnnoKeyEgressPort:  "456",
	}
	cfg, err = NewThrottleConfig(anno)
	assert.NoError(t, err)
	assert.Equal(t, uint64(2*1024*1024), cfg.Ingress.Bandwidth)
	assert.Equal(t, uint16(123), cfg.Ingress.Port)
	assert.Equal(t, uint64(3*1024), cfg.Egress.Bandwidth)
	assert.Equal(t, uint16(456), cfg.Egress.Port)

	anno[types.AnnoKeyIngress] = "wrong-bandwidth"
	_, err = NewThrottleConfig(anno)
	assert.Error(t, err)

	anno[types.AnnoKeyIngress] = "2mib/s"
	anno[types.AnnoKeyEgressPort] = "wrong-port"
	_, err = NewThrottleConfig(anno)
	assert.Error(t, err)

	anno[types.AnnoKeyEgressPort] = "456"
	delete(anno, types.AnnoKeyIngress)
	cfg, err = NewThrottleConfig(anno)
	assert.NoError(t, err)
	assert.Nil(t, cfg.Ingress)
	assert.NotNil(t, cfg.Egress)

	anno[types.AnnoKeyIngress] = "2mib/s"
	delete(anno, types.AnnoKeyEgressPort)
	_, err = NewThrottleConfig(anno)
	assert.Error(t, err) // now port must be specified
}

func TestCompare(t *testing.T) {
	assert.True(t, compare(nil, nil))
	assert.False(t, compare(&rules{}, nil))
	assert.False(t, compare(nil, &rules{}))
	assert.True(t, compare(&rules{}, &rules{}))

	assert.False(t, compare(&rules{ingress: &ingressRule{}}, &rules{}))
	assert.False(t, compare(&rules{}, &rules{ingress: &ingressRule{}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{}}, &rules{ingress: &ingressRule{}}))

	assert.False(t, compare(&rules{egress: &egressRule{}}, &rules{}))
	assert.False(t, compare(&rules{}, &rules{egress: &egressRule{}}))
	assert.True(t, compare(&rules{egress: &egressRule{}}, &rules{egress: &egressRule{}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{qdisc: &netlink.Ingress{}}},
		&rules{ingress: &ingressRule{}}))
	assert.False(t, compare(&rules{ingress: &ingressRule{}},
		&rules{ingress: &ingressRule{qdisc: &netlink.Ingress{}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{qdisc: &netlink.Ingress{}}},
		&rules{ingress: &ingressRule{qdisc: &netlink.Ingress{}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{filter: &netlink.U32{}}},
		&rules{ingress: &ingressRule{}}))
	assert.False(t, compare(&rules{ingress: &ingressRule{}},
		&rules{ingress: &ingressRule{filter: &netlink.U32{}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{filter: &netlink.U32{}}},
		&rules{ingress: &ingressRule{filter: &netlink.U32{}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{}}},
		&rules{ingress: &ingressRule{}}))
	assert.False(t, compare(&rules{ingress: &ingressRule{}},
		&rules{ingress: &ingressRule{common: &commonRule{}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{common: &commonRule{}}},
		&rules{ingress: &ingressRule{common: &commonRule{}}}))

	assert.False(t, compare(&rules{egress: &egressRule{common: &commonRule{}}},
		&rules{egress: &egressRule{}}))
	assert.False(t, compare(&rules{egress: &egressRule{}},
		&rules{egress: &egressRule{common: &commonRule{}}}))
	assert.True(t, compare(&rules{egress: &egressRule{common: &commonRule{}}},
		&rules{egress: &egressRule{common: &commonRule{}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{}}}},
		&rules{ingress: &ingressRule{common: &commonRule{}}}))
	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{}}},
		&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{}}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{}}}},
		&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{}}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{Defcls: 123}}}},
		&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{Defcls: 456}}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{Defcls: 123}}}},
		&rules{ingress: &ingressRule{common: &commonRule{qdisc: &netlink.Htb{Defcls: 123}}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{}}}},
		&rules{ingress: &ingressRule{common: &commonRule{}}}))
	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{}}},
		&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{}}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{}}}},
		&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{}}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{Rate: 123}}}},
		&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{Rate: 456}}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{Rate: 123}}}},
		&rules{ingress: &ingressRule{common: &commonRule{class: &netlink.HtbClass{Rate: 123}}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{filter: &netlink.U32{}}}},
		&rules{ingress: &ingressRule{common: &commonRule{}}}))
	assert.False(t, compare(&rules{ingress: &ingressRule{common: &commonRule{}}},
		&rules{ingress: &ingressRule{common: &commonRule{filter: &netlink.U32{}}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{common: &commonRule{filter: &netlink.U32{}}}},
		&rules{ingress: &ingressRule{common: &commonRule{filter: &netlink.U32{}}}}))

	assert.False(t, compare(&rules{ingress: &ingressRule{
		common: &commonRule{filter: &netlink.U32{Sel: WithSrcPort(nil, 123)}}}},
		&rules{ingress: &ingressRule{
			common: &commonRule{filter: &netlink.U32{Sel: WithSrcPort(nil, 456)}}}}))
	assert.True(t, compare(&rules{ingress: &ingressRule{
		common: &commonRule{filter: &netlink.U32{Sel: WithSrcPort(nil, 123)}}}},
		&rules{ingress: &ingressRule{
			common: &commonRule{filter: &netlink.U32{Sel: WithSrcPort(nil, 123)}}}}))
}
