//go:build userprefs
// +build userprefs

package extensions_test

import (
	"context"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/gorilla/mux"
	. "github.com/smartystreets/goconvey/convey"
	"gopkg.in/resty.v1"

	zerr "zotregistry.dev/zot/errors"
	"zotregistry.dev/zot/pkg/api"
	"zotregistry.dev/zot/pkg/api/config"
	"zotregistry.dev/zot/pkg/api/constants"
	"zotregistry.dev/zot/pkg/extensions"
	extconf "zotregistry.dev/zot/pkg/extensions/config"
	"zotregistry.dev/zot/pkg/log"
	mTypes "zotregistry.dev/zot/pkg/meta/types"
	test "zotregistry.dev/zot/pkg/test/common"
	"zotregistry.dev/zot/pkg/test/mocks"
)

var ErrTestError = errors.New("TestError")

func TestAllowedMethodsHeaderUserPrefs(t *testing.T) {
	defaultVal := true

	Convey("Test http options response", t, func() {
		conf := config.New()
		port := test.GetFreePort()
		conf.HTTP.Port = port
		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultVal
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultVal

		baseURL := test.GetBaseURL(port)

		ctlr := api.NewController(conf)
		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctrlManager := test.NewControllerManager(ctlr)

		ctrlManager.StartAndWait(port)
		defer ctrlManager.StopServer()

		resp, _ := resty.R().Options(baseURL + constants.FullUserPrefs)
		So(resp, ShouldNotBeNil)
		So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "PUT,OPTIONS")
		So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
	})
}

func TestHandlers(t *testing.T) {
	const UserprefsBaseURL = "http://127.0.0.1:8080/v2/_zot/ext/userprefs"

	log := log.NewLogger("debug", "")
	mockmetaDB := mocks.MetaDBMock{}

	Convey("No repo in request", t, func() {
		request := httptest.NewRequest(http.MethodGet, UserprefsBaseURL+"", strings.NewReader("My string"))
		response := httptest.NewRecorder()

		extensions.PutStar(response, request, mockmetaDB, log)
		res := response.Result()
		So(res.StatusCode, ShouldEqual, http.StatusBadRequest)

		defer res.Body.Close()

		extensions.PutBookmark(response, request, mockmetaDB, log)
		res = response.Result()
		So(res.StatusCode, ShouldEqual, http.StatusBadRequest)

		defer res.Body.Close()
	})

	Convey("Empty repo in request", t, func() {
		request := httptest.NewRequest(http.MethodGet, UserprefsBaseURL+"?repo=", strings.NewReader("My string"))
		response := httptest.NewRecorder()

		extensions.PutStar(response, request, mockmetaDB, log)
		res := response.Result()
		So(res.StatusCode, ShouldEqual, http.StatusNotFound)

		defer res.Body.Close()

		extensions.PutBookmark(response, request, mockmetaDB, log)
		res = response.Result()
		So(res.StatusCode, ShouldEqual, http.StatusNotFound)

		defer res.Body.Close()
	})

	Convey("ToggleStarRepo different errors", t, func() {
		request := httptest.NewRequest(http.MethodGet, UserprefsBaseURL+"?repo=test",
			strings.NewReader("My string"))

		Convey("ErrRepoMetaNotFound", func() {
			mockmetaDB.ToggleStarRepoFn = func(ctx context.Context, repo string) (mTypes.ToggleState, error) {
				return mTypes.NotChanged, zerr.ErrRepoMetaNotFound
			}

			mockmetaDB.ToggleBookmarkRepoFn = func(ctx context.Context, repo string) (mTypes.ToggleState, error) {
				return mTypes.NotChanged, zerr.ErrRepoMetaNotFound
			}

			response := httptest.NewRecorder()
			extensions.PutBookmark(response, request, mockmetaDB, log)

			res := response.Result()
			defer res.Body.Close()

			So(res.StatusCode, ShouldEqual, http.StatusNotFound)

			response = httptest.NewRecorder()
			extensions.PutStar(response, request, mockmetaDB, log)

			res = response.Result()
			defer res.Body.Close()

			So(res.StatusCode, ShouldEqual, http.StatusNotFound)
		})

		Convey("ErrUserDataNotAllowed", func() {
			request = mux.SetURLVars(request, map[string]string{
				"name": "repo",
			})

			mockmetaDB.ToggleBookmarkRepoFn = func(ctx context.Context, repo string) (mTypes.ToggleState, error) {
				return mTypes.NotChanged, zerr.ErrUserDataNotAllowed
			}

			mockmetaDB.ToggleStarRepoFn = func(ctx context.Context, repo string) (mTypes.ToggleState, error) {
				return mTypes.NotChanged, zerr.ErrUserDataNotAllowed
			}

			response := httptest.NewRecorder()
			extensions.PutBookmark(response, request, mockmetaDB, log)

			res := response.Result()
			defer res.Body.Close()

			So(res.StatusCode, ShouldEqual, http.StatusForbidden)

			response = httptest.NewRecorder()
			extensions.PutStar(response, request, mockmetaDB, log)

			res = response.Result()
			defer res.Body.Close()

			So(res.StatusCode, ShouldEqual, http.StatusForbidden)
		})

		Convey("ErrUnexpectedError", func() {
			request = mux.SetURLVars(request, map[string]string{
				"name": "repo",
			})

			mockmetaDB.ToggleBookmarkRepoFn = func(ctx context.Context, repo string) (mTypes.ToggleState, error) {
				return mTypes.NotChanged, ErrTestError
			}

			mockmetaDB.ToggleStarRepoFn = func(ctx context.Context, repo string) (mTypes.ToggleState, error) {
				return mTypes.NotChanged, ErrTestError
			}
			response := httptest.NewRecorder()
			extensions.PutBookmark(response, request, mockmetaDB, log)

			res := response.Result()
			defer res.Body.Close()

			So(res.StatusCode, ShouldEqual, http.StatusInternalServerError)

			response = httptest.NewRecorder()
			extensions.PutStar(response, request, mockmetaDB, log)

			res = response.Result()
			defer res.Body.Close()

			So(res.StatusCode, ShouldEqual, http.StatusInternalServerError)
		})
	})
}
