// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package generic

import (
	"context"
	"sort"
	"testing"

	"code.forgejo.org/f3/gof3/v3/id"
	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/path"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/tree/memory"

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

func NewMemoryTree(ctx context.Context, name string) generic.TreeInterface {
	return generic.GetFactory("memory")(ctx, memory.NewOptions(memory.NewIDAllocatorGenerator(name)))
}

func TestMemoryTreeIDAllocator(t *testing.T) {
	ctx := context.Background()

	name := "T"
	id := "THEID"

	for _, testCase := range []struct {
		idAllocator memory.IDAllocatorInterface
		setID       bool
		expectedID  string
	}{
		{
			idAllocator: memory.NewIDAllocatorGenerator(name),
			setID:       false,
			expectedID:  name + "-A",
		},
		{
			idAllocator: memory.NewIDAllocatorNull(),
			setID:       true,
			expectedID:  id,
		},
	} {
		t.Run(testCase.expectedID, func(t *testing.T) {
			tree := generic.GetFactory("memory")(ctx, memory.NewOptions(testCase.idAllocator))
			node := tree.GetRoot().CreateChild(ctx)
			f := memory.NewFormat("")
			if testCase.setID {
				f.SetID(id)
			}
			node.FromFormat(f)
			node.Upsert(ctx)
			assert.EqualValues(t, testCase.expectedID, node.GetID())
		})
	}
}

func testTreeBuild(t *testing.T, tree generic.TreeInterface, maxDepth int) {
	ctx := context.Background()

	insert := func(tree generic.TreeInterface, parent generic.NodeInterface) generic.NodeInterface {
		node := parent.CreateChild(ctx)
		node.Upsert(ctx)
		memory.SetContent(node, "content "+node.GetID().String())
		node.Upsert(ctx)
		return node
	}

	var populate func(depth int, tree generic.TreeInterface, parent generic.NodeInterface)
	populate = func(depth int, tree generic.TreeInterface, parent generic.NodeInterface) {
		if depth >= maxDepth {
			return
		}
		depth++
		for i := 1; i <= 3; i++ {
			node := insert(tree, parent)
			populate(depth, tree, node)
		}
	}

	populate(0, tree, insert(tree, tree.GetRoot()))
}

func TestMemoryTreeBuild(t *testing.T) {
	ctx := context.Background()

	verify := func(tree generic.TreeInterface, expected []string) {
		collected := make([]string, 0, 10)
		collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
			if node.GetKind() == kind.KindRoot {
				return
			}
			p = p.Append(node)
			collected = append(collected, p.String()+":"+memory.GetContent(node))
		}
		tree.WalkAndGet(ctx, generic.NewWalkOptions(collect))
		sort.Strings(expected)
		sort.Strings(collected)
		assert.EqualValues(t, expected, collected)
	}

	for _, testCase := range []struct {
		name       string
		build      func(tree generic.TreeInterface)
		operations func(tree generic.TreeInterface)
		expected   []string
	}{
		{
			name:       "full tree",
			build:      func(tree generic.TreeInterface) { testTreeBuild(t, tree, 2) },
			operations: func(tree generic.TreeInterface) {},
			expected:   []string{"/T-A/T-B/T-C:content T-C", "/T-A/T-B/T-D:content T-D", "/T-A/T-B/T-E:content T-E", "/T-A/T-B:content T-B", "/T-A/T-F/T-G:content T-G", "/T-A/T-F/T-H:content T-H", "/T-A/T-F/T-I:content T-I", "/T-A/T-F:content T-F", "/T-A/T-J/T-K:content T-K", "/T-A/T-J/T-L:content T-L", "/T-A/T-J/T-M:content T-M", "/T-A/T-J:content T-J", "/T-A:content T-A"},
		},
		{
			name:  "scenario 1",
			build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 2) },
			operations: func(tree generic.TreeInterface) {
				root := tree.GetRoot()

				id0 := id.NewNodeID("T-A")
				root.List(ctx)
				zero := root.GetChild(id0)
				assert.False(t, generic.NilNode == zero)
				assert.True(t, zero == zero.Get(ctx))

				id1 := id.NewNodeID("T-B")
				zero.List(ctx)
				one := zero.GetChild(id1)
				assert.False(t, generic.NilNode == one)
				one.Get(ctx)
				memory.SetContent(one, "other one")
				one.Upsert(ctx)

				id2 := id.NewNodeID("T-F")
				two := zero.GetChild(id2)
				two.Delete(ctx)
				two.Delete(ctx)
				assert.True(t, generic.NilNode == zero.GetChild(id2))
			},
			expected: []string{"/T-A/T-B/T-C:content T-C", "/T-A/T-B/T-D:content T-D", "/T-A/T-B/T-E:content T-E", "/T-A/T-B:other one", "/T-A/T-J/T-K:content T-K", "/T-A/T-J/T-L:content T-L", "/T-A/T-J/T-M:content T-M", "/T-A/T-J:content T-J", "/T-A:content T-A"},
		},
		{
			name:  "scenario 2",
			build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 0) },
			operations: func(tree generic.TreeInterface) {
				root := tree.GetRoot()

				id0 := id.NewNodeID("T-A")
				root.List(ctx)
				zero := root.GetChild(id0)
				assert.False(t, generic.NilNode == zero)
				zero.Get(ctx)

				one := zero.CreateChild(ctx)
				one.Upsert(ctx)
				memory.SetContent(one, "ONE")
				one.Upsert(ctx)

				two := one.CreateChild(ctx)
				two.Upsert(ctx)
				memory.SetContent(two, "SOMETHING")
				two.Upsert(ctx)
				memory.SetContent(two, "ONE/TWO")
				two.Upsert(ctx)
				one.DeleteChild(two.GetID())
				two.Get(ctx)

				three := two.CreateChild(ctx)
				three.Upsert(ctx)
				memory.SetContent(three, "ONE/THREE")
				three.Upsert(ctx)
				three.Delete(ctx)
			},
			expected: []string{"/T-A/T-B/T-C:ONE/TWO", "/T-A/T-B:ONE", "/T-A:content T-A"},
		},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			tree := NewMemoryTree(ctx, "T")

			tree.Trace("========== BUILD")
			testCase.build(tree)
			tree.Trace("========== OPERATIONS")
			testCase.operations(tree)
			verify(tree, testCase.expected)
			tree.Trace("========== VERIFY RELOAD")
			tree.Clear(ctx)
			verify(tree, testCase.expected)
		})
	}
}
