// +build integration

package sqlstore

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/grafana/grafana/pkg/models"
	. "github.com/smartystreets/goconvey/convey"
	"golang.org/x/oauth2"
)

//nolint:goconst
func TestUserAuth(t *testing.T) {
	InitTestDB(t)

	Convey("Given 5 users", t, func() {
		var err error
		var cmd *models.CreateUserCommand
		for i := 0; i < 5; i++ {
			cmd = &models.CreateUserCommand{
				Email: fmt.Sprint("user", i, "@test.com"),
				Name:  fmt.Sprint("user", i),
				Login: fmt.Sprint("loginuser", i),
			}
			err = CreateUser(context.Background(), cmd)
			So(err, ShouldBeNil)
		}

		Reset(func() {
			_, err := x.Exec("DELETE FROM org_user WHERE 1=1")
			So(err, ShouldBeNil)
			_, err = x.Exec("DELETE FROM org WHERE 1=1")
			So(err, ShouldBeNil)
			_, err = x.Exec("DELETE FROM " + dialect.Quote("user") + " WHERE 1=1")
			So(err, ShouldBeNil)
			_, err = x.Exec("DELETE FROM user_auth WHERE 1=1")
			So(err, ShouldBeNil)
		})

		Convey("Can find existing user", func() {
			// By Login
			login := "loginuser0"

			query := &models.GetUserByAuthInfoQuery{Login: login}
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, login)

			// By ID
			id := query.Result.Id

			query = &models.GetUserByAuthInfoQuery{UserId: id}
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Id, ShouldEqual, id)

			// By Email
			email := "user1@test.com"

			query = &models.GetUserByAuthInfoQuery{Email: email}
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Email, ShouldEqual, email)

			// Don't find nonexistent user
			email = "nonexistent@test.com"

			query = &models.GetUserByAuthInfoQuery{Email: email}
			err = GetUserByAuthInfo(query)

			So(err, ShouldEqual, models.ErrUserNotFound)
			So(query.Result, ShouldBeNil)
		})

		Convey("Can set & locate by AuthModule and AuthId", func() {
			// get nonexistent user_auth entry
			query := &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
			err = GetUserByAuthInfo(query)

			So(err, ShouldEqual, models.ErrUserNotFound)
			So(query.Result, ShouldBeNil)

			// create user_auth entry
			login := "loginuser0"

			query.Login = login
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, login)

			// get via user_auth
			query = &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, login)

			// get with non-matching id
			id := query.Result.Id

			query.UserId = id + 1
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, "loginuser1")

			// get via user_auth
			query = &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, "loginuser1")

			// remove user
			_, err = x.Exec("DELETE FROM "+dialect.Quote("user")+" WHERE id=?", query.Result.Id)
			So(err, ShouldBeNil)

			// get via user_auth for deleted user
			query = &models.GetUserByAuthInfoQuery{AuthModule: "test", AuthId: "test"}
			err = GetUserByAuthInfo(query)

			So(err, ShouldEqual, models.ErrUserNotFound)
			So(query.Result, ShouldBeNil)
		})

		Convey("Can set & retrieve oauth token information", func() {
			token := &oauth2.Token{
				AccessToken:  "testaccess",
				RefreshToken: "testrefresh",
				Expiry:       time.Now(),
				TokenType:    "Bearer",
			}

			// Find a user to set tokens on
			login := "loginuser0"

			// Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table
			query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test", AuthId: "test"}
			err = GetUserByAuthInfo(query)

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, login)

			cmd := &models.UpdateAuthInfoCommand{
				UserId:     query.Result.Id,
				AuthId:     query.AuthId,
				AuthModule: query.AuthModule,
				OAuthToken: token,
			}
			err = UpdateAuthInfo(cmd)

			So(err, ShouldBeNil)

			getAuthQuery := &models.GetAuthInfoQuery{
				UserId: query.Result.Id,
			}

			err = GetAuthInfo(getAuthQuery)

			So(err, ShouldBeNil)
			So(getAuthQuery.Result.OAuthAccessToken, ShouldEqual, token.AccessToken)
			So(getAuthQuery.Result.OAuthRefreshToken, ShouldEqual, token.RefreshToken)
			So(getAuthQuery.Result.OAuthTokenType, ShouldEqual, token.TokenType)
		})

		Convey("Always return the most recently used auth_module", func() {
			// Find a user to set tokens on
			login := "loginuser0"

			// Calling GetUserByAuthInfoQuery on an existing user will populate an entry in the user_auth table
			// Make the first log-in during the past
			getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) }
			query := &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test1", AuthId: "test1"}
			err = GetUserByAuthInfo(query)
			getTime = time.Now

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, login)

			// Add a second auth module for this user
			// Have this module's last log-in be more recent
			getTime = func() time.Time { return time.Now().AddDate(0, 0, -1) }
			query = &models.GetUserByAuthInfoQuery{Login: login, AuthModule: "test2", AuthId: "test2"}
			err = GetUserByAuthInfo(query)
			getTime = time.Now

			So(err, ShouldBeNil)
			So(query.Result.Login, ShouldEqual, login)

			// Get the latest entry by not supply an authmodule or authid
			getAuthQuery := &models.GetAuthInfoQuery{
				UserId: query.Result.Id,
			}

			err = GetAuthInfo(getAuthQuery)

			So(err, ShouldBeNil)
			So(getAuthQuery.Result.AuthModule, ShouldEqual, "test2")

			// "log in" again with the first auth module
			updateAuthCmd := &models.UpdateAuthInfoCommand{UserId: query.Result.Id, AuthModule: "test1", AuthId: "test1"}
			err = UpdateAuthInfo(updateAuthCmd)

			So(err, ShouldBeNil)

			// Get the latest entry by not supply an authmodule or authid
			getAuthQuery = &models.GetAuthInfoQuery{
				UserId: query.Result.Id,
			}

			err = GetAuthInfo(getAuthQuery)

			So(err, ShouldBeNil)
			So(getAuthQuery.Result.AuthModule, ShouldEqual, "test1")
		})
	})
}
