package session

import (
	"testing"

	"github.com/Kong/go-pdk"
	"github.com/Kong/go-pdk/bridge"
	"github.com/Kong/go-pdk/bridge/bridgetest"
	"github.com/Kong/go-pdk/client"
	"github.com/Kong/go-pdk/request"
	"github.com/Kong/go-pdk/server/kong_plugin_protocol"
	"github.com/form3tech-oss/jwt-go"
	"github.com/onsi/gomega"

	authsession "github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
)

func TestSkipCsrfTokenIncludesLogQuery(t *testing.T) {
	g := gomega.NewWithT(t)

	g.Expect(skipCsrfToken).To(gomega.ContainElement("/log/query"))
}

func TestMWTryAuthAttributesUISession(t *testing.T) {
	const secret = "session-secret"
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, &sessionTokenBody{
		Session: &Session{Username: "clinic-user"},
	})
	sessionID, err := token.SignedString([]byte(secret))
	if err != nil {
		t.Fatal(err)
	}

	getUserByUserNameFunc = func(username string) (*db.User, error) {
		gomega.NewWithT(t).Expect(username).To(gomega.Equal("clinic-user"))
		user := &db.User{UserName: username, Email: "clinic-user@example.com", Internal: true}
		user.ID = 23
		return user, nil
	}
	t.Cleanup(func() { getUserByUserNameFunc = model.GetUserByUserName })

	headers, err := bridge.WrapHeaders(map[string][]string{
		"Cookie": {sessionCookieName + "=" + sessionID},
	})
	if err != nil {
		t.Fatal(err)
	}
	pdkBridge := bridge.New(bridgetest.Mock(t, []bridgetest.MockStep{
		{Method: "kong.request.get_path", Ret: bridge.WrapString("/log/query")},
		{Method: "kong.request.get_headers", Args: &kong_plugin_protocol.Int{V: 100}, Ret: headers},
	}))
	kong := &pdk.PDK{Request: request.Request{PdkBridge: pdkBridge}}

	ctxUser, authErr := NewSessionManager(&config.SessionConfig{Secret: secret}, nil, true).MWTryAuth(kong)

	g := gomega.NewWithT(t)
	g.Expect(authErr).To(gomega.BeNil())
	g.Expect(ctxUser).ToNot(gomega.BeNil())
	g.Expect(ctxUser.UserID).To(gomega.Equal(uint(23)))
	g.Expect(ctxUser.AuthMethod).To(gomega.Equal(authsession.AuthMethodUISession))
}

func TestMWTryAuthReturnsVerifiedUISessionWhenIPIsRejected(t *testing.T) {
	const secret = "session-secret"
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, &sessionTokenBody{
		Session: &Session{Username: "clinic-user"},
	})
	sessionID, err := token.SignedString([]byte(secret))
	if err != nil {
		t.Fatal(err)
	}

	getUserByUserNameFunc = func(string) (*db.User, error) {
		user := &db.User{UserName: "clinic-user"}
		user.ID = 24
		return user, nil
	}
	needToLimitIPFunc = func(*authsession.CtxUser, string) bool { return true }
	t.Cleanup(func() {
		getUserByUserNameFunc = model.GetUserByUserName
		needToLimitIPFunc = authsession.NeedToLimitIP
	})

	headers, err := bridge.WrapHeaders(map[string][]string{
		"Cookie": {sessionCookieName + "=" + sessionID},
	})
	if err != nil {
		t.Fatal(err)
	}
	pdkBridge := bridge.New(bridgetest.Mock(t, []bridgetest.MockStep{
		{Method: "kong.request.get_path", Ret: bridge.WrapString("/log/query")},
		{Method: "kong.request.get_headers", Args: &kong_plugin_protocol.Int{V: 100}, Ret: headers},
		{Method: "kong.client.get_forwarded_ip", Ret: bridge.WrapString("127.0.0.1")},
	}))
	kong := &pdk.PDK{
		Client:  client.Client{PdkBridge: pdkBridge},
		Request: request.Request{PdkBridge: pdkBridge},
	}

	ctxUser, authErr := NewSessionManager(&config.SessionConfig{Secret: secret}, nil, false).MWTryAuth(kong)

	g := gomega.NewWithT(t)
	g.Expect(authErr).ToNot(gomega.BeNil())
	g.Expect(authErr.Code()).To(gomega.Equal(FORBIDDEN_ERROR_CODE))
	g.Expect(ctxUser).ToNot(gomega.BeNil())
	g.Expect(ctxUser.UserID).To(gomega.Equal(uint(24)))
	g.Expect(ctxUser.AuthMethod).To(gomega.Equal(authsession.AuthMethodUISession))
}
