package filetree

import (
	"testing"

	"github.com/jesseduffield/lazygit/pkg/commands/models"
	"github.com/stretchr/testify/assert"
)

func TestBuildTreeFromFiles(t *testing.T) {
	scenarios := []struct {
		name         string
		files        []*models.File
		showRootItem bool
		expected     *Node[models.File]
	}{
		{
			name:  "no files",
			files: []*models.File{},
			expected: &Node[models.File]{
				path:     "",
				Children: nil,
			},
		},
		{
			name: "files in same directory",
			files: []*models.File{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						path:             "./dir1",
						CompressionLevel: 1,
						Children: []*Node[models.File]{
							{
								File: &models.File{Path: "dir1/a"},
								path: "./dir1/a",
							},
							{
								File: &models.File{Path: "dir1/b"},
								path: "./dir1/b",
							},
						},
					},
				},
			},
		},
		{
			name: "files in same directory, not root item",
			files: []*models.File{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						path:             "dir1",
						CompressionLevel: 0,
						Children: []*Node[models.File]{
							{
								File: &models.File{Path: "dir1/a"},
								path: "dir1/a",
							},
							{
								File: &models.File{Path: "dir1/b"},
								path: "dir1/b",
							},
						},
					},
				},
			},
		},
		{
			name: "paths that can be compressed",
			files: []*models.File{
				{
					Path: "dir1/dir3/a",
				},
				{
					Path: "dir2/dir4/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						path: ".",
						Children: []*Node[models.File]{
							{
								path: "./dir1/dir3",
								Children: []*Node[models.File]{
									{
										File: &models.File{Path: "dir1/dir3/a"},
										path: "./dir1/dir3/a",
									},
								},
								CompressionLevel: 1,
							},
							{
								path: "./dir2/dir4",
								Children: []*Node[models.File]{
									{
										File: &models.File{Path: "dir2/dir4/b"},
										path: "./dir2/dir4/b",
									},
								},
								CompressionLevel: 1,
							},
						},
					},
				},
			},
		},
		{
			name: "paths that can be compressed, no root item",
			files: []*models.File{
				{
					Path: "dir1/dir3/a",
				},
				{
					Path: "dir2/dir4/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						path: "dir1/dir3",
						Children: []*Node[models.File]{
							{
								File: &models.File{Path: "dir1/dir3/a"},
								path: "dir1/dir3/a",
							},
						},
						CompressionLevel: 1,
					},
					{
						path: "dir2/dir4",
						Children: []*Node[models.File]{
							{
								File: &models.File{Path: "dir2/dir4/b"},
								path: "dir2/dir4/b",
							},
						},
						CompressionLevel: 1,
					},
				},
			},
		},
		{
			name: "paths that can be sorted",
			files: []*models.File{
				{
					Path: "b",
				},
				{
					Path: "a",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						path: ".",
						Children: []*Node[models.File]{
							{
								File: &models.File{Path: "a"},
								path: "./a",
							},
							{
								File: &models.File{Path: "b"},
								path: "./b",
							},
						},
					},
				},
			},
		},
		{
			name: "paths that can be sorted including a merge conflict file",
			files: []*models.File{
				{
					Path: "b",
				},
				{
					Path:              "z",
					HasMergeConflicts: true,
				},
				{
					Path: "a",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						path: ".",
						// it is a little strange that we're not bubbling up our merge conflict
						// here but we are technically still in tree mode and that's the rule
						Children: []*Node[models.File]{
							{
								File: &models.File{Path: "a"},
								path: "./a",
							},
							{
								File: &models.File{Path: "b"},
								path: "./b",
							},
							{
								File: &models.File{Path: "z", HasMergeConflicts: true},
								path: "./z",
							},
						},
					},
				},
			},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := BuildTreeFromFiles(s.files, s.showRootItem)
			assert.EqualValues(t, s.expected, result)
		})
	}
}

func TestBuildFlatTreeFromFiles(t *testing.T) {
	scenarios := []struct {
		name         string
		files        []*models.File
		showRootItem bool
		expected     *Node[models.File]
	}{
		{
			name:  "no files",
			files: []*models.File{},
			expected: &Node[models.File]{
				path:     "",
				Children: []*Node[models.File]{},
			},
		},
		{
			name: "files in same directory",
			files: []*models.File{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						File:             &models.File{Path: "dir1/a"},
						path:             "./dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.File{Path: "dir1/b"},
						path:             "./dir1/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "files in same directory, not root item",
			files: []*models.File{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						File:             &models.File{Path: "dir1/a"},
						path:             "dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.File{Path: "dir1/b"},
						path:             "dir1/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "paths that can be compressed",
			files: []*models.File{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir2/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						File:             &models.File{Path: "dir1/a"},
						path:             "./dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.File{Path: "dir2/b"},
						path:             "./dir2/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "paths that can be compressed, no root item",
			files: []*models.File{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir2/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						File:             &models.File{Path: "dir1/a"},
						path:             "dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.File{Path: "dir2/b"},
						path:             "dir2/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "paths that can be sorted",
			files: []*models.File{
				{
					Path: "b",
				},
				{
					Path: "a",
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						File: &models.File{Path: "a"},
						path: "./a",
					},
					{
						File: &models.File{Path: "b"},
						path: "./b",
					},
				},
			},
		},
		{
			name: "tracked, untracked, and conflicted files",
			files: []*models.File{
				{
					Path:    "a2",
					Tracked: false,
				},
				{
					Path:    "a1",
					Tracked: false,
				},
				{
					Path:              "c2",
					HasMergeConflicts: true,
				},
				{
					Path:              "c1",
					HasMergeConflicts: true,
				},
				{
					Path:    "b2",
					Tracked: true,
				},
				{
					Path:    "b1",
					Tracked: true,
				},
			},
			showRootItem: true,
			expected: &Node[models.File]{
				path: "",
				Children: []*Node[models.File]{
					{
						File: &models.File{Path: "c1", HasMergeConflicts: true},
						path: "./c1",
					},
					{
						File: &models.File{Path: "c2", HasMergeConflicts: true},
						path: "./c2",
					},
					{
						File: &models.File{Path: "b1", Tracked: true},
						path: "./b1",
					},
					{
						File: &models.File{Path: "b2", Tracked: true},
						path: "./b2",
					},
					{
						File: &models.File{Path: "a1", Tracked: false},
						path: "./a1",
					},
					{
						File: &models.File{Path: "a2", Tracked: false},
						path: "./a2",
					},
				},
			},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := BuildFlatTreeFromFiles(s.files, s.showRootItem)
			assert.EqualValues(t, s.expected, result)
		})
	}
}

func TestBuildTreeFromCommitFiles(t *testing.T) {
	scenarios := []struct {
		name         string
		files        []*models.CommitFile
		showRootItem bool
		expected     *Node[models.CommitFile]
	}{
		{
			name:  "no files",
			files: []*models.CommitFile{},
			expected: &Node[models.CommitFile]{
				path:     "",
				Children: nil,
			},
		},
		{
			name: "files in same directory",
			files: []*models.CommitFile{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						path:             "./dir1",
						CompressionLevel: 1,
						Children: []*Node[models.CommitFile]{
							{
								File: &models.CommitFile{Path: "dir1/a"},
								path: "./dir1/a",
							},
							{
								File: &models.CommitFile{Path: "dir1/b"},
								path: "./dir1/b",
							},
						},
					},
				},
			},
		},
		{
			name: "files in same directory, not root item",
			files: []*models.CommitFile{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						path:             "dir1",
						CompressionLevel: 0,
						Children: []*Node[models.CommitFile]{
							{
								File: &models.CommitFile{Path: "dir1/a"},
								path: "dir1/a",
							},
							{
								File: &models.CommitFile{Path: "dir1/b"},
								path: "dir1/b",
							},
						},
					},
				},
			},
		},
		{
			name: "paths that can be compressed",
			files: []*models.CommitFile{
				{
					Path: "dir1/dir3/a",
				},
				{
					Path: "dir2/dir4/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						path: ".",
						Children: []*Node[models.CommitFile]{
							{
								path: "./dir1/dir3",
								Children: []*Node[models.CommitFile]{
									{
										File: &models.CommitFile{Path: "dir1/dir3/a"},
										path: "./dir1/dir3/a",
									},
								},
								CompressionLevel: 1,
							},
							{
								path: "./dir2/dir4",
								Children: []*Node[models.CommitFile]{
									{
										File: &models.CommitFile{Path: "dir2/dir4/b"},
										path: "./dir2/dir4/b",
									},
								},
								CompressionLevel: 1,
							},
						},
					},
				},
			},
		},
		{
			name: "paths that can be compressed, no root item",
			files: []*models.CommitFile{
				{
					Path: "dir1/dir3/a",
				},
				{
					Path: "dir2/dir4/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						path: "dir1/dir3",
						Children: []*Node[models.CommitFile]{
							{
								File: &models.CommitFile{Path: "dir1/dir3/a"},
								path: "dir1/dir3/a",
							},
						},
						CompressionLevel: 1,
					},
					{
						path: "dir2/dir4",
						Children: []*Node[models.CommitFile]{
							{
								File: &models.CommitFile{Path: "dir2/dir4/b"},
								path: "dir2/dir4/b",
							},
						},
						CompressionLevel: 1,
					},
				},
			},
		},
		{
			name: "paths that can be sorted",
			files: []*models.CommitFile{
				{
					Path: "b",
				},
				{
					Path: "a",
				},
			},
			showRootItem: true,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						path: ".",
						Children: []*Node[models.CommitFile]{
							{
								File: &models.CommitFile{Path: "a"},
								path: "./a",
							},
							{
								File: &models.CommitFile{Path: "b"},
								path: "./b",
							},
						},
					},
				},
			},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := BuildTreeFromCommitFiles(s.files, s.showRootItem)
			assert.EqualValues(t, s.expected, result)
		})
	}
}

func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
	scenarios := []struct {
		name         string
		files        []*models.CommitFile
		showRootItem bool
		expected     *Node[models.CommitFile]
	}{
		{
			name:  "no files",
			files: []*models.CommitFile{},
			expected: &Node[models.CommitFile]{
				path:     "",
				Children: []*Node[models.CommitFile]{},
			},
		},
		{
			name: "files in same directory",
			files: []*models.CommitFile{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						File:             &models.CommitFile{Path: "dir1/a"},
						path:             "./dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.CommitFile{Path: "dir1/b"},
						path:             "./dir1/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "files in same directory, not root item",
			files: []*models.CommitFile{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir1/b",
				},
			},
			showRootItem: false,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						File:             &models.CommitFile{Path: "dir1/a"},
						path:             "dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.CommitFile{Path: "dir1/b"},
						path:             "dir1/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "paths that can be compressed",
			files: []*models.CommitFile{
				{
					Path: "dir1/a",
				},
				{
					Path: "dir2/b",
				},
			},
			showRootItem: true,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						File:             &models.CommitFile{Path: "dir1/a"},
						path:             "./dir1/a",
						CompressionLevel: 0,
					},
					{
						File:             &models.CommitFile{Path: "dir2/b"},
						path:             "./dir2/b",
						CompressionLevel: 0,
					},
				},
			},
		},
		{
			name: "paths that can be sorted",
			files: []*models.CommitFile{
				{
					Path: "b",
				},
				{
					Path: "a",
				},
			},
			showRootItem: true,
			expected: &Node[models.CommitFile]{
				path: "",
				Children: []*Node[models.CommitFile]{
					{
						File: &models.CommitFile{Path: "a"},
						path: "./a",
					},
					{
						File: &models.CommitFile{Path: "b"},
						path: "./b",
					},
				},
			},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			result := BuildFlatTreeFromCommitFiles(s.files, s.showRootItem)
			assert.EqualValues(t, s.expected, result)
		})
	}
}
