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

package f3

import (
	"context"
	"crypto/sha256"
	"fmt"
	"io"
	"path/filepath"
	"sort"
	"strings"
	"testing"

	"code.forgejo.org/f3/gof3/v3/f3"
	filesystem_options "code.forgejo.org/f3/gof3/v3/forges/filesystem/options"
	tests_repository "code.forgejo.org/f3/gof3/v3/forges/helpers/tests/repository"
	"code.forgejo.org/f3/gof3/v3/options"
	"code.forgejo.org/f3/gof3/v3/path"
	f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	tests_forge "code.forgejo.org/f3/gof3/v3/tree/tests/f3/forge"

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

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

	for _, factory := range tests_forge.GetFactories() {
		testForge := factory()
		t.Run(testForge.GetName(), func(t *testing.T) {
			// testCase.options will t.Skip if the forge instance is not up
			forgeWriteOptions := testForge.NewOptions(t)
			forgeReadOptions := testForge.NewOptions(t)
			forgeReadOptions.(options.URLInterface).SetURL(forgeWriteOptions.(options.URLInterface).GetURL())

			fixtureTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
			fixtureTree.Trace("======= build fixture")
			TreeBuildPartial(t, "F3Mirror"+testForge.GetName(), testForge.GetKindExceptions(), forgeWriteOptions, fixtureTree)

			fixtureTree.Trace("======= mirror fixture to forge")

			forgeWriteTree := generic.GetFactory("f3")(ctx, forgeWriteOptions)
			generic.TreeMirror(ctx, fixtureTree, forgeWriteTree, generic.NewPathFromString(""), generic.NewMirrorOptions())

			paths := []string{""}
			if testForge.GetName() != filesystem_options.Name {
				paths = []string{"/forge/users/10111", "/forge/users/20222"}
			}
			pathPairs := make([][2]path.Path, 0, 5)
			for _, p := range paths {
				p := generic.NewPathFromString(p)
				pathPairs = append(pathPairs, [2]path.Path{p, generic.TreePathRemap(ctx, fixtureTree, p, forgeWriteTree)})
			}

			fixtureTree.Trace("======= read from forge")

			forgeReadTree := generic.GetFactory("f3")(ctx, forgeReadOptions)
			forgeReadTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))

			fixtureTree.Trace("======= mirror forge to filesystem")

			verificationTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))

			for _, pathPair := range pathPairs {
				generic.TreeMirror(ctx, forgeReadTree, verificationTree, pathPair[1], generic.NewMirrorOptions())
			}

			fixtureTree.Trace("======= compare fixture with forge mirrored to filesystem")
			for _, pathPair := range pathPairs {
				assert.True(t, generic.TreeCompare(ctx, fixtureTree, pathPair[0], verificationTree, pathPair[1]))
			}

			TreeDelete(t, testForge.GetNonTestUsers(), forgeWriteOptions, forgeWriteTree)
		})
	}
}

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

	verify := func(tree generic.TreeInterface, expected []string) {
		collected := make([]string, 0, 10)
		collect := func(ctx context.Context, path path.Path, node generic.NodeInterface) {
			path = path.Append(node)
			collected = append(collected, path.String())
		}
		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) { TreeBuild(t, "F3", nil, tree) },
			operations: func(tree generic.TreeInterface) {},
			expected: []string{
				"",
				"/forge",
				"/forge/organizations",
				"/forge/organizations/3330001",
				"/forge/organizations/3330001/projects",
				"/forge/topics",
				"/forge/topics/14411441",
				"/forge/users",
				"/forge/users/10111",
				"/forge/users/10111/projects",
				"/forge/users/10111/projects/74823",
				"/forge/users/10111/projects/74823/issues",
				"/forge/users/10111/projects/74823/issues/1234567",
				"/forge/users/10111/projects/74823/issues/1234567/comments",
				"/forge/users/10111/projects/74823/issues/1234567/comments/1111999",
				"/forge/users/10111/projects/74823/issues/1234567/comments/1111999/attachments",
				"/forge/users/10111/projects/74823/issues/1234567/comments/1111999/reactions",
				"/forge/users/10111/projects/74823/issues/1234567/attachments",
				"/forge/users/10111/projects/74823/issues/1234567/reactions",
				"/forge/users/10111/projects/74823/issues/1234567/reactions/1212",
				"/forge/users/10111/projects/74823/labels",
				"/forge/users/10111/projects/74823/labels/7777",
				"/forge/users/10111/projects/74823/labels/99999",
				"/forge/users/10111/projects/74823/milestones",
				"/forge/users/10111/projects/74823/milestones/7888",
				"/forge/users/10111/projects/74823/pull_requests",
				"/forge/users/10111/projects/74823/pull_requests/2222",
				"/forge/users/10111/projects/74823/pull_requests/2222/attachments",
				"/forge/users/10111/projects/74823/pull_requests/2222/comments",
				"/forge/users/10111/projects/74823/pull_requests/2222/reactions",
				"/forge/users/10111/projects/74823/pull_requests/2222/reviews",
				"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593",
				"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reactions",
				"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reviewcomments",
				"/forge/users/10111/projects/74823/pull_requests/2222/reviews/4593/reviewcomments/9876543",
				"/forge/users/10111/projects/74823/releases",
				"/forge/users/10111/projects/74823/releases/123",
				"/forge/users/10111/projects/74823/releases/123/attachments",
				"/forge/users/10111/projects/74823/releases/123/attachments/585858",
				"/forge/users/10111/projects/74823/repositories",
				"/forge/users/10111/projects/74823/repositories/vcs",
				"/forge/users/20222",
				"/forge/users/20222/projects",
				"/forge/users/20222/projects/99099",
				"/forge/users/20222/projects/99099/issues",
				"/forge/users/20222/projects/99099/labels",
				"/forge/users/20222/projects/99099/milestones",
				"/forge/users/20222/projects/99099/pull_requests",
				"/forge/users/20222/projects/99099/releases",
				"/forge/users/20222/projects/99099/repositories",
				"/forge/users/20222/projects/99099/repositories/vcs",
			},
		},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			tree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))

			tree.Trace("======= BUILD")
			testCase.build(tree)
			tree.Trace("======= OPERATIONS")
			testCase.operations(tree)

			tree.Trace("======= VERIFY")
			verify(tree, testCase.expected)

			tree.Clear(ctx)

			tree.Trace("======= VERIFY STEP 2")
			verify(tree, testCase.expected)

			tree.Trace("======= COMPARE")
			otherTree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))
			otherTree.GetOptions().(options.URLInterface).SetURL(tree.GetOptions().(options.URLInterface).GetURL())
			otherTree.WalkAndGet(ctx, generic.NewWalkOptions(nil))
			assert.True(t, generic.TreeCompare(ctx, tree, generic.NewPathFromString(""), otherTree, generic.NewPathFromString("")))
		})
	}
}

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

	verify := func(tree generic.TreeInterface, name string) {
		repositoryPath := generic.NewPathFromString(filepath.Join("/forge/users/10111/projects/74823/repositories", name))
		found := false
		tree.Apply(ctx, repositoryPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
			node.Get(ctx)
			node.List(ctx)
			if node.GetKind() != f3_tree.KindRepository {
				return
			}
			helper := tests_repository.NewTestHelper(t, "", node)
			helper.AssertRepositoryFileExists("README.md")
			if name == f3.RepositoryNameDefault {
				helper.AssertRepositoryTagExists("releasetagv12")
				helper.AssertRepositoryBranchExists("feature")
			}
			found = true
		}).SetWhere(generic.ApplyEachNode))
		assert.True(t, found)
	}

	for _, name := range []string{f3.RepositoryNameDefault} {
		t.Run(name, func(t *testing.T) {
			tree := generic.GetFactory("f3")(ctx, tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t))

			tree.Trace("======= BUILD")
			TreeBuild(t, "F3", nil, tree)

			tree.Trace("======= VERIFY")
			verify(tree, name)

			tree.Clear(ctx)
			tree.Trace("======= VERIFY STEP 2")
			verify(tree, name)
		})
	}
}

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

	content := "OTHER CONTENT"
	expectedSHA256 := fmt.Sprintf("%x", sha256.Sum256([]byte(content)))
	opts := tests_forge.GetFactory(filesystem_options.Name)().NewOptions(t)
	{
		tree := generic.GetFactory("f3")(ctx, opts)
		TreeBuild(t, "F3", nil, tree)

		attachmentPath := generic.NewPathFromString("/forge/users/10111/projects/74823/releases/123/attachments/585858")
		attachment := tree.Find(attachmentPath)
		assert.False(t, attachment.GetIsNil())

		attachmentFormat := attachment.ToFormat().(*f3.Attachment)
		attachmentFormat.DownloadFunc = func() io.ReadCloser {
			rc := io.NopCloser(strings.NewReader(content))
			return rc
		}

		attachment.FromFormat(attachmentFormat)
		attachment.Upsert(ctx)
		attachmentFormat = attachment.ToFormat().(*f3.Attachment)
		assert.EqualValues(t, expectedSHA256, attachmentFormat.SHA256)
	}
	{
		tree := generic.GetFactory("f3")(ctx, opts)
		tree.WalkAndGet(ctx, generic.NewWalkOptions(nil))

		attachmentPath := generic.NewPathFromString("/forge/users/10111/projects/74823/releases/123/attachments/585858")
		attachment := tree.Find(attachmentPath)
		assert.False(t, attachment.GetIsNil())

		attachmentFormat := attachment.ToFormat().(*f3.Attachment)
		assert.EqualValues(t, expectedSHA256, attachmentFormat.SHA256)
	}
}
