package api

import (
	"encoding/json"
	"net/http"
	"testing"
	"time"

	"github.com/pquerna/otp/totp"
	"github.com/stretchr/testify/assert"
	"github.com/tidwall/gjson"

	"github.com/photoprism/photoprism/internal/config"
	"github.com/photoprism/photoprism/internal/form"
)

func TestCreateUserPasscode(t *testing.T) {
	t.Run("PublicMode", func(t *testing.T) {
		app, router, _ := NewApiTest()
		CreateUserPasscode(router)
		r := PerformRequest(app, "POST", "/api/v1/users/uqxc08w3d0ej2283/passcode")
		assert.Equal(t, http.StatusForbidden, r.Code)
	})
	t.Run("Unauthorized", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		CreateUserPasscode(router)

		r := PerformRequest(app, "POST", "/api/v1/users/uqxc08w3d0ej2283/passcode")
		assert.Equal(t, http.StatusUnauthorized, r.Code)
	})
	t.Run("UsersDontMatch", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		CreateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "Alice123!",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxc08w3d0ej2283/passcode", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
	t.Run("AliceUnsupportedType", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		CreateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "xxx",
			Password: "abcdef",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode", string(pcStr), sessId)
			assert.Equal(t, http.StatusBadRequest, r.Code)
		}
	})
	t.Run("AliceInvalidPassword", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		CreateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "wrong",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
	t.Run("AliceSuccess", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		CreateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "Alice123!",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode", string(pcStr), sessId)
			assert.Equal(t, http.StatusCreated, r.Code)
			assert.Equal(t, "/api/v1/users/uqxetse3cy5eo9z2/passcode", r.Header().Get("Location"))
		}
	})
}

func TestConfirmUserPasscode(t *testing.T) {
	t.Run("PublicMode", func(t *testing.T) {
		app, router, _ := NewApiTest()
		ConfirmUserPasscode(router)
		r := PerformRequest(app, "POST", "/api/v1/users/uqxc08w3d0ej2283/passcode/confirm")
		assert.Equal(t, http.StatusForbidden, r.Code)
	})
	t.Run("AlicePasscodeTooShort", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		ConfirmUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "Alice123!",
			Code:     "123",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/confirm", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
	t.Run("AliceInvalidPassCode", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		ConfirmUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "Alice123!",
			Code:     "123456",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/confirm", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
}

func TestActivateUserPasscode(t *testing.T) {
	t.Run("UsersDontMatch", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		ActivateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "Alice123!",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxc08w3d0ej2283/passcode/activate", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
	t.Run("InvalidPasscode", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		ActivateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "Alice123!",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/activate", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
}

func TestDeactivateUserPasscode(t *testing.T) {
	t.Run("Unauthorized", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		DeactivateUserPasscode(router)

		r := PerformRequest(app, "POST", "/api/v1/users/uqxc08w3d0ej2283/passcode/deactivate")
		assert.Equal(t, http.StatusUnauthorized, r.Code)
	})
	t.Run("AliceInvalidPassword", func(t *testing.T) {
		app, router, conf := NewApiTest()
		conf.SetAuthMode(config.AuthModePasswd)
		defer conf.SetAuthMode(config.AuthModePublic)
		DeactivateUserPasscode(router)
		sessId := AuthenticateUser(app, router, "alice", "Alice123!")

		f := form.Passcode{
			Type:     "totp",
			Password: "wrong",
			Code:     "",
		}
		if pcStr, err := json.Marshal(f); err != nil {
			log.Fatal(err)
		} else {
			r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/deactivate", string(pcStr), sessId)
			assert.Equal(t, http.StatusForbidden, r.Code)
		}
	})
}

func TestUserPasscode(t *testing.T) {
	// create
	app, router, conf := NewApiTest()
	conf.SetAuthMode(config.AuthModePasswd)
	defer conf.SetAuthMode(config.AuthModePublic)
	CreateUserPasscode(router)
	sessId := AuthenticateUser(app, router, "alice", "Alice123!")

	f0 := form.Passcode{
		Type:     "totp",
		Password: "Alice123!",
		Code:     "",
	}

	pcStr, err := json.Marshal(f0)

	if err != nil {
		t.Fatalf("marshal passcode: %v", err)
	}

	r := AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode", string(pcStr), sessId)
	assert.Equal(t, http.StatusCreated, r.Code)

	secret := gjson.Get(r.Body.String(), "Secret").String()
	activatedAt := gjson.Get(r.Body.String(), "ActivatedAt").String()
	verifiedAt := gjson.Get(r.Body.String(), "VerifiedAt").String()

	assert.Empty(t, activatedAt)
	assert.Empty(t, verifiedAt)

	code, err := totp.GenerateCode(secret, time.Now())

	// confirm
	ConfirmUserPasscode(router)

	if err != nil {
		t.Fatalf("generate totp code: %v", err)
	}

	f := form.Passcode{
		Type:     "totp",
		Password: "Alice123!",
		Code:     code,
	}

	pcStr, err = json.Marshal(f)

	if err != nil {
		t.Fatalf("marshal passcode confirm: %v", err)
	}

	r = AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/confirm", string(pcStr), sessId)
	assert.Equal(t, http.StatusOK, r.Code)

	activatedAt = gjson.Get(r.Body.String(), "ActivatedAt").String()
	verifiedAt = gjson.Get(r.Body.String(), "VerifiedAt").String()

	assert.Empty(t, activatedAt)
	assert.NotEmpty(t, verifiedAt)

	// activate
	ActivateUserPasscode(router)

	r = AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/activate", string(pcStr), sessId)
	assert.Equal(t, http.StatusOK, r.Code)

	activatedAt = gjson.Get(r.Body.String(), "ActivatedAt").String()
	verifiedAt = gjson.Get(r.Body.String(), "VerifiedAt").String()

	assert.NotEmpty(t, activatedAt)
	assert.NotEmpty(t, verifiedAt)

	// deactivate
	DeactivateUserPasscode(router)

	r = AuthenticatedRequestWithBody(app, "POST", "/api/v1/users/uqxetse3cy5eo9z2/passcode/deactivate", string(pcStr), sessId)
	assert.Equal(t, http.StatusOK, r.Code)
}
