package main

import (
	"errors"
	"slices"
	"testing"

	"go.uber.org/mock/gomock"
	"src.opensuse.org/autogits/common"
	mock_common "src.opensuse.org/autogits/common/mock"
)

func TestMaintainership(t *testing.T) {
	config := common.AutogitConfig{
		Branch:         "bar",
		Organization:   "foo",
		GitProjectName: common.DefaultGitPrj,
	}

	packageTests := []struct {
		name        string
		maintainers []string
		otherError  bool
		packageName string

		maintainersFile    []byte
		maintainersFileErr error

		maintainersDir map[string][]byte
	}{
		/* PACKAGE MAINTAINERS */
		// package tests have packageName, projects do not
		{
			name:        "No maintainer in empty package",
			packageName: "foo",
		},
		{
			name:               "Error in MaintainerListForPackage when remote has an error",
			maintainersFileErr: errors.New("some error here"),
			packageName:        "foo",
		},
		{
			name:            "Multiple package maintainers",
			maintainersFile: []byte(`{"pkg": ["user1", "user2"],    "": ["user1", "user3"]}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": ["user1", "user3"]}`),
				"pkg":      []byte(`{"pkg": ["user1", "user2"]}`),
			},
			maintainers: []string{"user1", "user2", "user3"},
			packageName: "pkg",
		},
		{
			name:            "No package maintainers and only project maintainer",
			maintainersFile: []byte(`{"pkg2": ["user1", "user2"],   "": ["user1", "user3"]}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": ["user1", "user3"]}`),
			},
			maintainers: []string{"user1", "user3"},
			packageName: "pkg",
		},
		{
			name:            "Invalid list of package maintainers",
			maintainersFile: []byte(`{"pkg": 3,"": ["user", 4]}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": ["user1", 4]}`),
				"pkg":      []byte(`"pkg": 3`),
			},
			otherError:  true,
			packageName: "pkg",
		},

		/* PROJECT MAINTAINERS */
		{
			name: "No maintainer for empty project",
		},
		{
			name:            "No maintainer for empty project maintainer file",
			maintainersFile: []byte("{}"),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{}`),
			},
		},
		{
			name:               "Error in MaintainerListForProject when remote has an error",
			maintainersFileErr: errors.New("some error here"),
		},
		{
			name:            "Multiple project maintainers",
			maintainersFile: []byte(`{"": ["user1", "user2"]}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": ["user1", "user2"]}`),
			},
			maintainers: []string{"user1", "user2"},
		},
		{
			name:            "Single project maintainer",
			maintainersFile: []byte(`{"": ["user"]}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": ["user"]}`),
			},
			maintainers: []string{"user"},
		},
		{
			name:            "Invalid list of project maintainers",
			maintainersFile: []byte(`{"": ["user", 4]}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": ["user", 4]}`),
			},
			otherError: true,
		},
		{
			name:            "Invalid list of project maintainers",
			maintainersFile: []byte(`{"": 4}`),
			maintainersDir: map[string][]byte{
				"_project": []byte(`{"": 4}`),
			},
			otherError: true,
		},
	}

	notFoundError := errors.New("not found")
	for _, test := range packageTests {
		runTests := func(t *testing.T, mi common.GiteaMaintainershipInterface) {
			maintainers, err := FetchProjectMaintainershipData(mi, config.Organization, config.GitProjectName, config.Branch)
			if err != nil && !test.otherError {
				if test.maintainersFileErr == nil {
					t.Fatal("Unexpected error recieved", err)
				} else if err != test.maintainersFileErr {
					t.Error("Wrong error recieved", err)
				}
			} else if test.maintainersFileErr != nil {
				t.Fatal("Expected an error...")
			} else if test.otherError && err == nil {
				t.Fatal("Expected an error...")
			}

			var m []string
			if len(test.packageName) > 0 {
				m = maintainers.ListPackageMaintainers(test.packageName)
			} else {
				m = maintainers.ListProjectMaintainers()
			}

			if len(m) != len(test.maintainers) {
				t.Error("Invalid number of maintainers for package", test.packageName, len(m), "vs", len(test.maintainers))
			}
			for i := range m {
				if !slices.Contains(test.maintainers, m[i]) {
					t.Fatal("Can't find expected users. Found:", m)
				}
			}
		}

		t.Run(test.name+"_File", func(t *testing.T) {
			ctl := gomock.NewController(t)
			mi := mock_common.NewMockGiteaMaintainershipInterface(ctl)

			// tests with maintainership file
			mi.EXPECT().FetchMaintainershipFile("foo", common.DefaultGitPrj, "bar").
				Return(test.maintainersFile, test.maintainersFileErr)
			mi.EXPECT().FetchMaintainershipDirFile("foo", common.DefaultGitPrj, "bar", ProjectFileKey).
				Return(nil, notFoundError)

			runTests(t, mi)
		})

		t.Run(test.name+"_Dir", func(t *testing.T) {
			ctl := gomock.NewController(t)
			mi := mock_common.NewMockGiteaMaintainershipInterface(ctl)

			// run same tests with directory maintainership data
			for filename, data := range test.maintainersDir {
				mi.EXPECT().FetchMaintainershipDirFile("foo", common.DefaultGitPrj, "bar", filename).Return(data, test.maintainersFileErr).AnyTimes()
			}
			if _, found := test.maintainersDir[ProjectFileKey]; !found {
				mi.EXPECT().FetchMaintainershipDirFile("foo", common.DefaultGitPrj, "bar", ProjectFileKey).Return(nil, test.maintainersFileErr).AnyTimes()
				mi.EXPECT().FetchMaintainershipFile("foo", common.DefaultGitPrj, "bar").Return(nil, test.maintainersFileErr).AnyTimes()
			}
			mi.EXPECT().FetchMaintainershipDirFile("foo", common.DefaultGitPrj, "bar", gomock.Any()).Return(nil, notFoundError).AnyTimes()

			runTests(t, mi)
		})
	}
}

func TestMaintainershipDir(t *testing.T) {
}
