// SPDX-FileCopyrightText: 2026 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

//go:build !js

package sctp

import (
	"net"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func udpPiper(t *testing.T) (net.Conn, net.Conn) {
	t.Helper()

	return createUDPConnPair()
}

func TestAssociationOptions_ApplyBothSides(t *testing.T) {
	opts := []AssociationOption{
		WithName("x"),
		WithMaxReassemblyQueueEntries(13),
	}

	var sCfg Config
	var cCfg Config

	for _, opt := range opts {
		assert.NoError(t, opt.applyServer(&sCfg))
		assert.NoError(t, opt.applyClient(&cCfg))
	}

	assert.Equal(t, "x", sCfg.Name)
	assert.Equal(t, "x", cCfg.Name)
	assert.Equal(t, uint32(13), sCfg.maxReassemblyQueueEntries)
	assert.Equal(t, uint32(13), cCfg.maxReassemblyQueueEntries)
}

func TestConfigComparable(t *testing.T) {
	_ = map[Config]struct{}{}
}

func TestAssociationOptions_Interleaving(t *testing.T) {
	ca, cb := udpPiper(t)
	defer func() {
		_ = ca.Close()
		_ = cb.Close()
	}()

	defaultCfg, err := buildClientConfig(WithNetConn(ca))
	assert.NoError(t, err)
	assert.True(t, defaultCfg.enableInterleaving)

	disabledCfg, err := buildServerConfig(WithNetConn(cb), WithEnableInterleaving(false))
	assert.NoError(t, err)
	assert.False(t, disabledCfg.enableInterleaving)
	assert.True(t, disabledCfg.enableInterleavingSet)
}

func TestAssociationOptions_DefaultMTU(t *testing.T) {
	ca, cb := udpPiper(t)
	defer func() {
		_ = ca.Close()
		_ = cb.Close()
	}()

	cfg, err := buildClientConfig(WithNetConn(ca))
	assert.NoError(t, err)
	assert.Equal(t, uint32(1191), cfg.MTU)

	assoc := createAssociationFromConfigWithTsn(cfg, 1)
	defer assoc.close() //nolint:errcheck
	assert.Equal(t, uint32(1191), assoc.MTU())
	assert.Equal(t, maxPayloadSizeForMTU(initialMTU, false), assoc.maxPayloadSize)
}

func TestAssociationOptions_Validation(t *testing.T) {
	t.Run("nil logger factory", func(t *testing.T) {
		var cfg Config
		err := WithLoggerFactory(nil).applyServer(&cfg)
		assert.ErrorIs(t, err, errNilLoggerFactory)
	})

	t.Run("nil net conn", func(t *testing.T) {
		var cfg Config
		err := WithNetConn(nil).applyServer(&cfg)
		assert.ErrorIs(t, err, errNilNetConn)
	})

	t.Run("mtu zero", func(t *testing.T) {
		var cfg Config
		err := WithMTU(0).applyServer(&cfg)
		assert.ErrorIs(t, err, errZeroMTUOption)
	})

	t.Run("mtu too small", func(t *testing.T) {
		minMTU := commonHeaderSize + iDataChunkHeaderSize + paddingMultiple

		var cfg Config
		err := WithMTU(minMTU - 1).applyServer(&cfg)
		assert.ErrorIs(t, err, errMTUTooSmallOption)

		err = Config{MTU: minMTU - 1}.applyClient(&cfg)
		assert.ErrorIs(t, err, errMTUTooSmallOption)

		err = WithMTU(minMTU).applyServer(&cfg)
		assert.NoError(t, err)
		assert.Equal(t, minMTU, cfg.MTU)
	})

	t.Run("max recv buf zero", func(t *testing.T) {
		var cfg Config
		err := WithMaxReceiveBufferSize(0).applyServer(&cfg)
		assert.ErrorIs(t, err, errZeroMaxReceiveBufferOption)
	})

	t.Run("max msg size zero", func(t *testing.T) {
		var cfg Config
		err := WithMaxMessageSize(0).applyServer(&cfg)
		assert.ErrorIs(t, err, errZeroMaxMessageSize)
	})

	t.Run("rto max <= 0", func(t *testing.T) {
		var cfg Config
		err := WithRTOMax(0).applyServer(&cfg)
		assert.ErrorIs(t, err, errInvalidRTOMax)
	})

	t.Run("snap nil arguments", func(t *testing.T) {
		var cfg Config
		err := WithSNAP(nil, nil).applyServer(&cfg)
		assert.ErrorIs(t, err, errInvalidSnapToken)
		err = WithSNAP([]byte{}, []byte{}).applyServer(&cfg)
		assert.ErrorIs(t, err, errInvalidSnapToken)
	})

	t.Run("nil stream scheduler", func(t *testing.T) {
		var cfg Config
		err := WithInterleavingOptions(WithInterleavingStreamSchedulerFactory(nil)).applyServer(&cfg)
		assert.ErrorIs(t, err, errNilStreamScheduler)
	})

	t.Run("invalid stream scheduler weight", func(t *testing.T) {
		var cfg Config
		err := WithInterleavingOptions(
			WithInterleavingWeightedFairQueueingWeight(1, 0),
		).applyServer(&cfg)
		assert.ErrorIs(t, err, errInvalidStreamSchedulerWeight)
	})
}

func TestClientWithOptions_ValidatesOptionValues(t *testing.T) {
	t.Run("missing net conn", func(t *testing.T) {
		_, err := ClientWithOptions(WithName("no-net-conn"))
		assert.ErrorIs(t, err, errNilNetConn)
	})

	t.Run("invalid option value", func(t *testing.T) {
		ca, cb := udpPiper(t)
		defer func() {
			_ = ca.Close()
			_ = cb.Close()
		}()

		_, err := ClientWithOptions(WithNetConn(ca), WithMTU(0))
		assert.ErrorIs(t, err, errZeroMTUOption)
	})
}

func TestServerWithOptions_ValidatesOptionValues(t *testing.T) {
	t.Run("missing net conn", func(t *testing.T) {
		_, err := ServerWithOptions(WithName("no-net-conn"))
		assert.ErrorIs(t, err, errNilNetConn)
	})

	t.Run("invalid option value", func(t *testing.T) {
		ca, cb := udpPiper(t)
		defer func() {
			_ = ca.Close()
			_ = cb.Close()
		}()

		_, err := ServerWithOptions(WithNetConn(ca), WithRTOMax(0))
		assert.ErrorIs(t, err, errInvalidRTOMax)
	})
}

func TestAssociationOptions_ClientAndServer(t *testing.T) {
	lf := customLogger{
		expectZeroChecksum: true,
		t:                  t,
	}

	aClient, aServer, err := association(
		t,
		udpPiper,
		WithName("opt-pair"),
		WithLoggerFactory(lf),

		WithMTU(1200),
		WithMaxReceiveBufferSize(7777),
		WithMaxMessageSize(30000),
		WithMaxReassemblyQueueEntries(13),
		WithRTOMax(1000),
		WithMinCwnd(5000),
		WithFastRtxWnd(6000),
		WithCwndCAStep(7000),
		WithBlockWrite(true),
		WithEnableZeroChecksum(true),
		WithEnableInterleaving(false),
	)
	assert.NoError(t, err)
	defer func() {
		_ = aClient.Close()
		_ = aServer.Close()
	}()

	_, ok := aClient.log.(customLogger)
	assert.True(t, ok, "client logger should be customLogger")
	_, ok = aServer.log.(customLogger)
	assert.True(t, ok, "server logger should be customLogger")

	assert.Equal(t, uint32(1200), aClient.MTU())
	assert.Equal(t, uint32(1200), aServer.MTU())

	assert.Equal(t, uint32(7777), aClient.maxReceiveBufferSize)
	assert.Equal(t, uint32(7777), aServer.maxReceiveBufferSize)

	assert.Equal(t, uint32(30000), aClient.MaxMessageSize())
	assert.Equal(t, uint32(30000), aServer.MaxMessageSize())

	assert.Equal(t, uint32(13), aClient.maxReassemblyQueueEntries)
	assert.Equal(t, uint32(13), aServer.maxReassemblyQueueEntries)

	assert.Equal(t, uint32(5000), aClient.minCwnd)
	assert.Equal(t, uint32(6000), aClient.fastRtxWnd)
	assert.Equal(t, uint32(7000), aClient.cwndCAStep)

	assert.True(t, aClient.blockWrite)
	assert.True(t, aServer.blockWrite)

	assert.True(t, aClient.recvZeroChecksum)
	assert.True(t, aServer.recvZeroChecksum)

	assert.Equal(t, maxPayloadSizeForMTU(1200, false), aClient.maxPayloadSize)
	assert.Equal(t, maxPayloadSizeForMTU(1200, false), aServer.maxPayloadSize)

	assert.Equal(t, "opt-pair", aClient.name)
	assert.Equal(t, "opt-pair", aServer.name)

	time.Sleep(10 * time.Millisecond)
}
