package htlcswitch

import (
	"testing"

	"github.com/lightningnetwork/lnd/channeldb/models"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/stretchr/testify/require"
)

func TestHeldHtlcSetEmpty(t *testing.T) {
	set := newHeldHtlcSet()

	// Test operations on an empty set.
	require.False(t, set.exists(models.CircuitKey{}))

	_, err := set.pop(models.CircuitKey{})
	require.Error(t, err)

	set.popAll(
		func(_ InterceptedForward) {
			require.Fail(t, "unexpected fwd")
		},
	)
}

func TestHeldHtlcSet(t *testing.T) {
	set := newHeldHtlcSet()

	key := models.CircuitKey{
		ChanID: lnwire.NewShortChanIDFromInt(1),
		HtlcID: 2,
	}

	// Test pushing a nil forward.
	require.Error(t, set.push(key, nil))

	// Test pushing a forward.
	fwd := &interceptedForward{
		htlc: &lnwire.UpdateAddHTLC{},
	}
	require.NoError(t, set.push(key, fwd))

	// Re-pushing should fail.
	require.Error(t, set.push(key, fwd))

	// Test popping the fwd.
	poppedFwd, err := set.pop(key)
	require.NoError(t, err)
	require.Equal(t, fwd, poppedFwd)

	_, err = set.pop(key)
	require.Error(t, err)

	// Pushing the forward again.
	require.NoError(t, set.push(key, fwd))

	// Test for each.
	var cbCalled bool
	set.forEach(func(_ InterceptedForward) {
		cbCalled = true

		require.Equal(t, fwd, poppedFwd)
	})
	require.True(t, cbCalled)

	// Test popping all forwards.
	cbCalled = false
	set.popAll(
		func(_ InterceptedForward) {
			cbCalled = true

			require.Equal(t, fwd, poppedFwd)
		},
	)
	require.True(t, cbCalled)

	_, err = set.pop(key)
	require.Error(t, err)
}

func TestHeldHtlcSetAutoFails(t *testing.T) {
	set := newHeldHtlcSet()

	key := models.CircuitKey{
		ChanID: lnwire.NewShortChanIDFromInt(1),
		HtlcID: 2,
	}

	const autoFailHeight = 100
	fwd := &interceptedForward{
		packet:         &htlcPacket{},
		htlc:           &lnwire.UpdateAddHTLC{},
		autoFailHeight: autoFailHeight,
	}
	require.NoError(t, set.push(key, fwd))

	// Test popping auto fails up to one block before the auto-fail height
	// of our forward.
	set.popAutoFails(
		autoFailHeight-1,
		func(_ InterceptedForward) {
			require.Fail(t, "unexpected fwd")
		},
	)

	// Popping succeeds at the auto-fail height.
	cbCalled := false
	set.popAutoFails(
		autoFailHeight,
		func(poppedFwd InterceptedForward) {
			cbCalled = true

			require.Equal(t, fwd, poppedFwd)
		},
	)
	require.True(t, cbCalled)

	// After this, there should be nothing more to pop.
	set.popAutoFails(
		autoFailHeight,
		func(_ InterceptedForward) {
			require.Fail(t, "unexpected fwd")
		},
	)
}
