package praefect

import (
	"context"
	"net"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
	"gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage"
	"gitlab.com/gitlab-org/gitaly/v16/internal/grpc/protoregistry"
	"gitlab.com/gitlab-org/gitaly/v16/internal/grpc/proxy"
	"gitlab.com/gitlab-org/gitaly/v16/internal/praefect/config"
	"gitlab.com/gitlab-org/gitaly/v16/internal/praefect/datastore"
	"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
	"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper"
	"gitlab.com/gitlab-org/gitaly/v16/internal/testhelper/testdb"
	"gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func TestRepositoryExistsHandler(t *testing.T) {
	t.Parallel()

	errServedByGitaly := structerr.NewInternal("request passed to Gitaly")

	db := testdb.New(t)
	for _, tc := range []struct {
		desc          string
		routeToGitaly bool
		repository    *gitalypb.Repository
		response      *gitalypb.RepositoryExistsResponse
		error         error
	}{
		{
			desc:  "missing repository",
			error: structerr.NewInvalidArgument("%w", storage.ErrRepositoryNotSet),
		},
		{
			desc:       "missing storage name",
			repository: &gitalypb.Repository{RelativePath: "relative-path"},
			error:      structerr.NewInvalidArgument("%w", storage.ErrStorageNotSet),
		},
		{
			desc:       "missing relative path",
			repository: &gitalypb.Repository{StorageName: "virtual-storage"},
			error:      structerr.NewInvalidArgument("%w", storage.ErrRepositoryPathNotSet),
		},
		{
			desc:       "invalid virtual storage",
			repository: &gitalypb.Repository{StorageName: "invalid virtual storage", RelativePath: "relative-path"},
			response:   &gitalypb.RepositoryExistsResponse{Exists: false},
		},
		{
			desc:       "invalid relative path",
			repository: &gitalypb.Repository{StorageName: "virtual-storage", RelativePath: "invalid relative path"},
			response:   &gitalypb.RepositoryExistsResponse{Exists: false},
		},
		{
			desc:       "repository found",
			repository: &gitalypb.Repository{StorageName: "virtual-storage", RelativePath: "relative-path"},
			response:   &gitalypb.RepositoryExistsResponse{Exists: true},
		},
		{
			desc:          "routed to gitaly",
			routeToGitaly: true,
			error:         errServedByGitaly,
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			db.TruncateAll(t)
			rs := datastore.NewPostgresRepositoryStore(db, map[string][]string{"virtual-storage": {"storage"}})
			ctx := testhelper.Context(t)

			require.NoError(t, rs.CreateRepository(ctx, 0, "virtual-storage", "relative-path", "relative-path", "storage", nil, nil, false, false))

			tmp := testhelper.TempDir(t)

			ln, err := net.Listen("unix", filepath.Join(tmp, "praefect"))
			require.NoError(t, err)

			electionStrategy := config.ElectionStrategyPerRepository
			if tc.routeToGitaly {
				electionStrategy = config.ElectionStrategySQL
			}

			srv := NewGRPCServer(&Dependencies{
				Config: config.Config{Failover: config.Failover{ElectionStrategy: electionStrategy}},
				Logger: testhelper.SharedLogger(t),
				Director: func(ctx context.Context, fullMethodName string, peeker proxy.StreamPeeker) (*proxy.StreamParameters, error) {
					return nil, errServedByGitaly
				},
				RepositoryStore: rs,
				Registry:        protoregistry.GitalyProtoPreregistered,
			}, nil)
			defer srv.Stop()

			go testhelper.MustServe(t, srv, ln)

			clientConn, err := grpc.DialContext(ctx, "unix://"+ln.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
			require.NoError(t, err)
			defer testhelper.MustClose(t, clientConn)

			client := gitalypb.NewRepositoryServiceClient(clientConn)
			_, err = client.RepositorySize(ctx, &gitalypb.RepositorySizeRequest{Repository: tc.repository})
			testhelper.RequireGrpcError(t, errServedByGitaly, err)

			resp, err := client.RepositoryExists(ctx, &gitalypb.RepositoryExistsRequest{Repository: tc.repository})
			testhelper.RequireGrpcError(t, tc.error, err)
			testhelper.ProtoEqual(t, tc.response, resp)
		})
	}
}
