package middleware

import (
	"context"
	"net/http"
	"net/http/httptest"

	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
	gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
	rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/owncloud/ocis/v2/ocis-pkg/log"
	"google.golang.org/grpc"
)

var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), func() {
	var authenticator Authenticator
	BeforeEach(func() {
		authenticator = PublicShareAuthenticator{
			Logger: log.NewLogger(),
			RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient](
				"GatewaySelector",
				"com.owncloud.api.gateway",
				func(cc *grpc.ClientConn) gateway.GatewayAPIClient {
					return mockGatewayClient{
						AuthenticateFunc: func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code) {
							if authType != "publicshares" {
								return "", rpcv1beta1.Code_CODE_NOT_FOUND
							}

							if clientID == "sharetoken" && (clientSecret == "password|examples3cr3t" || clientSecret == "signature|examplesignature|exampleexpiration") {
								return "exampletoken", rpcv1beta1.Code_CODE_OK
							}

							if clientID == "sharetoken" && clientSecret == "password|" {
								return "otherexampletoken", rpcv1beta1.Code_CODE_OK
							}

							return "", rpcv1beta1.Code_CODE_NOT_FOUND
						},
					}
				},
			),
		}
	})
	When("the request contains correct data", func() {
		Context("using password authentication", func() {
			It("should successfully authenticate", func() {
				req := httptest.NewRequest(http.MethodGet, "http://example.com/dav/public-files/?public-token=sharetoken", http.NoBody)
				req.SetBasicAuth("public", "examples3cr3t")

				req2, valid := authenticator.Authenticate(req)

				Expect(valid).To(Equal(true))
				Expect(req2).ToNot(BeNil())

				h := req2.Header
				Expect(h.Get(_headerRevaAccessToken)).To(Equal("exampletoken"))
			})
		})
		Context("using signature authentication", func() {
			It("should successfully authenticate", func() {
				req := httptest.NewRequest(http.MethodGet, "http://example.com/dav/public-files/?public-token=sharetoken&signature=examplesignature&expiration=exampleexpiration", http.NoBody)

				req2, valid := authenticator.Authenticate(req)

				Expect(valid).To(Equal(true))
				Expect(req2).ToNot(BeNil())

				h := req2.Header
				Expect(h.Get(_headerRevaAccessToken)).To(Equal("exampletoken"))
			})
		})
	})
	When("the reguest is for the archiver", func() {
		Context("using a public-token", func() {
			It("should successfully authenticate", func() {
				req := httptest.NewRequest(http.MethodGet, "http://example.com/archiver?public-token=sharetoken", http.NoBody)
				req2, valid := authenticator.Authenticate(req)

				Expect(valid).To(Equal(true))
				Expect(req2).ToNot(BeNil())

				h := req2.Header
				Expect(h.Get(_headerRevaAccessToken)).To(Equal("otherexampletoken"))
			})
		})
		Context("not using a public-token", func() {
			It("should fail to authenticate", func() {
				req := httptest.NewRequest(http.MethodGet, "http://example.com/archiver", http.NoBody)
				req2, valid := authenticator.Authenticate(req)

				Expect(valid).To(Equal(false))
				Expect(req2).To(BeNil())
			})
		})
	})
})

type mockGatewayClient struct {
	gatewayv1beta1.GatewayAPIClient
	AuthenticateFunc func(authType, clientID, clientSecret string) (string, rpcv1beta1.Code)
}

func (c mockGatewayClient) Authenticate(ctx context.Context, in *gatewayv1beta1.AuthenticateRequest, opts ...grpc.CallOption) (*gatewayv1beta1.AuthenticateResponse, error) {
	token, code := c.AuthenticateFunc(in.GetType(), in.GetClientId(), in.GetClientSecret())
	return &gatewayv1beta1.AuthenticateResponse{
		Status: &rpcv1beta1.Status{Code: code},
		Token:  token,
	}, nil
}
