package task_test

import (
	"context"
	"encoding/json"
	"errors"
	"time"

	apiGateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
	apiUser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
	apiRpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
	apiProvider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
	apiTypes "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/qsfera/server/services/storage-users/pkg/task"
	"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
	"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
	"github.com/opencloud-eu/reva/v2/pkg/utils"
	cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks"
	"github.com/stretchr/testify/mock"
	"google.golang.org/grpc"
)

func MustMarshal(v any) []byte {
	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return b
}

var _ = Describe("trash", func() {
	var (
		gatewayClient             *cs3mocks.GatewayAPIClient
		gatewaySelector           pool.Selectable[gateway.GatewayAPIClient]
		ctx                       context.Context
		now                       time.Time
		genericError              error
		user                      *apiUser.User
		getUserResponse           *apiUser.GetUserResponse
		authenticateResponse      *apiGateway.AuthenticateResponse
		listStorageSpacesResponse *apiProvider.ListStorageSpacesResponse
		personalSpace             *apiProvider.StorageSpace
		projectSpace              *apiProvider.StorageSpace
		virtualSpace              *apiProvider.StorageSpace
	)

	BeforeEach(func() {
		pool.RemoveSelector("GatewaySelector" + "qsfera.api.gateway")
		gatewayClient = &cs3mocks.GatewayAPIClient{}
		gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient](
			"GatewaySelector",
			"qsfera.api.gateway",
			func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
				return gatewayClient
			},
		)

		ctx = context.Background()
		now = time.Now()
		genericError = errors.New("any")
		getUserResponse = &apiUser.GetUserResponse{
			Status: status.NewOK(ctx),
		}
		authenticateResponse = &apiGateway.AuthenticateResponse{
			Status: status.NewOK(ctx),
			Token:  "",
		}
		listStorageSpacesResponse = &apiProvider.ListStorageSpacesResponse{
			Status:        status.NewOK(ctx),
			StorageSpaces: []*apiProvider.StorageSpace{},
		}
		personalSpace = &apiProvider.StorageSpace{
			SpaceType: "personal",
			Id: &apiProvider.StorageSpaceId{
				OpaqueId: "personal",
			},
			Root: &apiProvider.ResourceId{
				OpaqueId: "personal",
			},
		}
		projectSpace = &apiProvider.StorageSpace{
			SpaceType: "project",
			Id: &apiProvider.StorageSpaceId{
				OpaqueId: "project",
			},
			Root: &apiProvider.ResourceId{
				OpaqueId: "project",
			},
			Opaque: &apiTypes.Opaque{},
		}
		// virtual is here as an example,
		// the task ignores all space types expect `project` and `personal`.
		virtualSpace = &apiProvider.StorageSpace{
			SpaceType: "virtual",
			Id: &apiProvider.StorageSpaceId{
				OpaqueId: "virtual",
			},
			Root: &apiProvider.ResourceId{
				OpaqueId: "virtual",
			},
		}
		user = &apiUser.User{
			Id: &apiUser.UserId{
				OpaqueId: "user",
			},
		}

	})

	Describe("PurgeTrashBin", func() {
		It("throws an error if the user cannot authenticate", func() {
			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
			gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(nil, genericError)

			err := task.PurgeTrashBin("service-user-id", now, task.Project, gatewaySelector, "")
			Expect(err).To(HaveOccurred())
		})
		It("throws an error if space listing fails", func() {
			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
			gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil)
			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(nil, genericError)

			err := task.PurgeTrashBin("service-user-id", now, task.Project, gatewaySelector, "")
			Expect(err).To(HaveOccurred())
		})
		It("only deletes items older than the specified period", func() {
			var (
				recycleItems = map[string][]*apiProvider.RecycleItem{
					"personal": {
						{Key: "now", DeletionTime: utils.TimeToTS(now)},
						{Key: "after", DeletionTime: utils.TimeToTS(now.Add(1 * time.Second))},
						{Key: "before", DeletionTime: utils.TimeToTS(now.Add(-1 * time.Second))},
					},
					"project": {
						{Key: "now", DeletionTime: utils.TimeToTS(now)},
						{Key: "after", DeletionTime: utils.TimeToTS(now.Add(1 * time.Minute))},
						{Key: "before", DeletionTime: utils.TimeToTS(now.Add(-1 * time.Minute))},
					},
					"virtual": {
						{Key: "now", DeletionTime: utils.TimeToTS(now)},
						{Key: "after", DeletionTime: utils.TimeToTS(now.Add(1 * time.Hour))},
						{Key: "before", DeletionTime: utils.TimeToTS(now.Add(-1 * time.Hour))},
					},
				}
			)

			personalSpace.Owner = user
			listStorageSpacesResponse.StorageSpaces = []*apiProvider.StorageSpace{
				personalSpace,
				projectSpace,
				virtualSpace,
			}
			projectSpace.Opaque.Map = map[string]*apiTypes.OpaqueEntry{
				"grants": {
					Decoder: "json",
					Value: MustMarshal(map[string]*apiProvider.ResourcePermissions{
						"admin": {
							Delete: true,
						},
					}),
				},
			}

			gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(getUserResponse, nil)
			gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(authenticateResponse, nil)
			gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(listStorageSpacesResponse, nil)
			gatewayClient.On("ListRecycle", mock.Anything, mock.Anything).Return(
				func(_ context.Context, req *apiProvider.ListRecycleRequest, _ ...grpc.CallOption) *apiProvider.ListRecycleResponse {
					return &apiProvider.ListRecycleResponse{
						RecycleItems: recycleItems[req.Ref.ResourceId.OpaqueId],
					}
				}, nil,
			)
			gatewayClient.On("PurgeRecycle", mock.Anything, mock.Anything).Return(
				func(_ context.Context, req *apiProvider.PurgeRecycleRequest, _ ...grpc.CallOption) *apiProvider.PurgeRecycleResponse {
					var items []*apiProvider.RecycleItem
					for _, item := range recycleItems[req.Ref.ResourceId.OpaqueId] {
						if req.Key == item.Key {
							continue
						}

						items = append(items, item)
					}

					recycleItems[req.Ref.ResourceId.OpaqueId] = items

					return &apiProvider.PurgeRecycleResponse{
						Status: &apiRpc.Status{
							Code: apiRpc.Code_CODE_OK,
						},
					}
				}, nil,
			)

			err := task.PurgeTrashBin("service-user-id", now, task.Project, gatewaySelector, "")
			Expect(err).To(BeNil())
			Expect(recycleItems["personal"]).To(HaveLen(2))
			Expect(recycleItems["project"]).To(HaveLen(2))
			// virtual spaces are ignored
			Expect(recycleItems["virtual"]).To(HaveLen(3))
		})
	})
})
