// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package state

import (
	"sync"
	"testing"

	"github.com/hashicorp/go-memdb"
	"github.com/stretchr/testify/require"
)

type pti struct {
	Id string
}

func TestPruneOld(t *testing.T) {
	schema := &memdb.DBSchema{
		Tables: map[string]*memdb.TableSchema{
			"items": {
				Name: "items",
				Indexes: map[string]*memdb.IndexSchema{
					jobIdIndexName: {
						Name:         "id",
						AllowMissing: false,
						Unique:       true,
						Indexer: &memdb.StringFieldIndex{
							Field: "Id",
						},
					},
				},
			},
		},
	}

	t.Run("prunes none when there is enough room", func(t *testing.T) {
		require := require.New(t)

		inmem, err := memdb.NewMemDB(schema)
		require.NoError(err)

		txn := inmem.Txn(true)
		defer txn.Abort()

		require.NoError(txn.Insert("items", &pti{"B"}))
		require.NoError(txn.Insert("items", &pti{"C"}))
		require.NoError(txn.Insert("items", &pti{"A"}))
		require.NoError(txn.Insert("items", &pti{"D"}))

		// Weird order on purpose to validate lexical ordering on pruning.

		txn.Commit()

		txn = inmem.Txn(true)
		defer txn.Abort()

		var (
			mu      sync.Mutex
			indexed int = 4
		)

		cnt, err := pruneOld(txn, pruneOp{
			lock:      &mu,
			table:     "items",
			index:     "id",
			indexArgs: []interface{}{""},
			cur:       &indexed,
			max:       4,
		})
		require.NoError(err)

		txn.Commit()

		require.Equal(0, cnt)
		require.Equal(4, indexed)

		txn = inmem.Txn(false)
		defer txn.Abort()

		val, err := txn.First("items", "id", "A")
		require.NoError(err)
		require.NotNil(val)

		val, err = txn.First("items", "id", "B")
		require.NoError(err)
		require.NotNil(val)

		val, err = txn.First("items", "id", "C")
		require.NoError(err)
		require.NotNil(val)

		val, err = txn.First("items", "id", "D")
		require.NoError(err)
		require.NotNil(val)
	})

	t.Run("deletes a subset of records", func(t *testing.T) {
		require := require.New(t)

		inmem, err := memdb.NewMemDB(schema)
		require.NoError(err)

		txn := inmem.Txn(true)
		defer txn.Abort()

		require.NoError(txn.Insert("items", &pti{"B"}))
		require.NoError(txn.Insert("items", &pti{"C"}))
		require.NoError(txn.Insert("items", &pti{"A"}))
		require.NoError(txn.Insert("items", &pti{"D"}))

		// Weird order on purpose to validate lexical ordering on pruning.

		txn.Commit()

		txn = inmem.Txn(true)
		defer txn.Abort()

		var (
			mu      sync.Mutex
			indexed int = 4
		)

		cnt, err := pruneOld(txn, pruneOp{
			lock:      &mu,
			table:     "items",
			index:     "id",
			indexArgs: []interface{}{""},
			cur:       &indexed,
			max:       2,
		})
		require.NoError(err)

		txn.Commit()

		require.Equal(2, cnt)
		require.Equal(2, indexed)

		txn = inmem.Txn(false)
		defer txn.Abort()

		val, err := txn.First("items", "id", "A")
		require.NoError(err)
		require.Nil(val)

		val, err = txn.First("items", "id", "B")
		require.NoError(err)
		require.Nil(val)

		val, err = txn.First("items", "id", "C")
		require.NoError(err)
		require.NotNil(val)

		val, err = txn.First("items", "id", "D")
		require.NoError(err)
		require.NotNil(val)
	})

	t.Run("deletes all records", func(t *testing.T) {
		require := require.New(t)

		inmem, err := memdb.NewMemDB(schema)
		require.NoError(err)

		txn := inmem.Txn(true)
		defer txn.Abort()

		require.NoError(txn.Insert("items", &pti{"B"}))
		require.NoError(txn.Insert("items", &pti{"C"}))
		require.NoError(txn.Insert("items", &pti{"A"}))
		require.NoError(txn.Insert("items", &pti{"D"}))

		// Weird order on purpose to validate lexical ordering on pruning.

		txn.Commit()

		txn = inmem.Txn(true)
		defer txn.Abort()

		var (
			mu      sync.Mutex
			indexed int = 4
		)

		cnt, err := pruneOld(txn, pruneOp{
			lock:      &mu,
			table:     "items",
			index:     "id",
			indexArgs: []interface{}{""},
			cur:       &indexed,
			max:       0,
		})
		require.NoError(err)

		txn.Commit()

		require.Equal(4, cnt)
		require.Equal(0, indexed)

		txn = inmem.Txn(false)
		defer txn.Abort()

		val, err := txn.First("items", "id", "A")
		require.NoError(err)
		require.Nil(val)

		val, err = txn.First("items", "id", "B")
		require.NoError(err)
		require.Nil(val)

		val, err = txn.First("items", "id", "C")
		require.NoError(err)
		require.Nil(val)

		val, err = txn.First("items", "id", "D")
		require.NoError(err)
		require.Nil(val)
	})

}
