package tigin

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

	"github.com/gin-gonic/gin"
	"github.com/golang/mock/gomock"
	"github.com/smartystreets/goconvey/convey"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"

	"github.com/tidbcloud/tiinf/errs"
)

func TestCheckUserAndPermission(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockedAccountClient := account.NewMockAccountAPIServiceClient(ctrl)
	m := CheckUserAndPermission(mockedAccountClient)

	dummy := func(c *gin.Context) {
		_, err := GetCurrentUser(c)
		if err != nil {
			AbortWithError(c, errs.NewInternalDB("should get user but not", nil))
			return
		}
	}
	user := &account.User{Id: 1008611}

	engine := Default()
	engine.Use(m)
	engine.GET("/api/org/:org_id/project/:project_id", dummy)
	engine.GET("/api/org/:org_id", dummy)
	engine.GET("/api/hello", dummy)

	convey.Convey("TestCheckUserAndPermission", t, func() {
		convey.Convey("no token", func() {
			w := httptest.NewRecorder()
			req, _ := http.NewRequest("GET", "/api/hello", nil)
			engine.ServeHTTP(w, req)

			convey.So(w.Code, convey.ShouldEqual, http.StatusUnauthorized)
		})

		convey.Convey("invalid token", func() {
			w := httptest.NewRecorder()
			req, _ := http.NewRequest("GET", "/api/hello", nil)
			req.Header.Set("Authorization", "this that well emmm")
			engine.ServeHTTP(w, req)

			convey.So(w.Code, convey.ShouldEqual, http.StatusUnauthorized)
		})

		convey.Convey("invalid token type", func() {
			w := httptest.NewRecorder()
			req, _ := http.NewRequest("GET", "/api/hello", nil)
			req.Header.Set("Authorization", "this that")
			engine.ServeHTTP(w, req)

			convey.So(w.Code, convey.ShouldEqual, http.StatusUnauthorized)
		})

		convey.Convey("get user by token failed", func() {
			convey.Convey("invalid token", func() {
				w := httptest.NewRecorder()
				req, _ := http.NewRequest("GET", "/api/hello", nil)
				req.Header.Set("Authorization", "this that")
				engine.ServeHTTP(w, req)

				convey.So(w.Code, convey.ShouldEqual, http.StatusUnauthorized)
			})

			convey.Convey("get user by token failed", func() {
				mockedAccountClient.EXPECT().GetUserByToken(gomock.Any(), gomock.Any()).Return(&account.GetUserByTokenResponse{User: user}, errors.New("some error"))

				w := httptest.NewRecorder()
				req, _ := http.NewRequest("GET", "/api/hello", nil)
				req.Header.Set("Authorization", "Bearer that")
				engine.ServeHTTP(w, req)

				convey.So(w.Code, convey.ShouldEqual, http.StatusUnauthorized)
			})
		})

		convey.Convey("has org_id", func() {
			convey.Convey("get org failed", func() {
				mockedAccountClient.EXPECT().GetUserByToken(gomock.Any(), gomock.Any()).Return(&account.GetUserByTokenResponse{User: user}, nil)
				mockedAccountClient.EXPECT().ListOrgsByUser(gomock.Any(), gomock.Any()).Return(&account.ListOrgsByUserResponse{}, errors.New("some error"))

				w := httptest.NewRecorder()
				req, _ := http.NewRequest("GET", "/api/org/1", nil)
				req.Header.Set("Authorization", "Bearer that")
				engine.ServeHTTP(w, req)

				convey.So(w.Code, convey.ShouldEqual, http.StatusForbidden)
			})
			convey.Convey("no org found", func() {
				mockedAccountClient.EXPECT().GetUserByToken(gomock.Any(), gomock.Any()).Return(&account.GetUserByTokenResponse{User: user}, nil)
				mockedAccountClient.EXPECT().ListOrgsByUser(gomock.Any(), gomock.Any()).Return(&account.ListOrgsByUserResponse{}, nil)

				w := httptest.NewRecorder()
				req, _ := http.NewRequest("GET", "/api/org/1", nil)
				req.Header.Set("Authorization", "Bearer that")
				engine.ServeHTTP(w, req)

				convey.So(w.Code, convey.ShouldEqual, http.StatusForbidden)
			})
		})

		convey.Convey("has org_id and project id", func() {
			mockedAccountClient.EXPECT().GetUserByToken(gomock.Any(), gomock.Any()).Return(&account.GetUserByTokenResponse{User: user}, nil)
			mockedAccountClient.EXPECT().ListOrgsByUser(gomock.Any(), gomock.Any()).Return(&account.ListOrgsByUserResponse{Orgs: []*account.Org{
				{Id: 1},
			}}, nil)

			convey.Convey("get project failed", func() {
				mockedAccountClient.EXPECT().ListProjectsByOrgsInUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(&account.ListProjectsByOrgsInUserResponse{}, errors.New("some error"))

				w := httptest.NewRecorder()
				req, _ := http.NewRequest("GET", "/api/org/1/project/2", nil)
				req.Header.Set("Authorization", "Bearer that")
				engine.ServeHTTP(w, req)

				convey.So(w.Code, convey.ShouldEqual, http.StatusForbidden)
			})
			convey.Convey("no project found", func() {
				mockedAccountClient.EXPECT().ListProjectsByOrgsInUser(gomock.Any(), gomock.Any(), gomock.Any()).Return(&account.ListProjectsByOrgsInUserResponse{}, nil)

				w := httptest.NewRecorder()
				req, _ := http.NewRequest("GET", "/api/org/1/project/2", nil)
				req.Header.Set("Authorization", "Bearer that")
				engine.ServeHTTP(w, req)

				convey.So(w.Code, convey.ShouldEqual, http.StatusForbidden)
			})
		})
	})
}
