package auth_test

import (
	"context"
	"net/http"
	"testing"

	"allaboutapps.dev/aw/go-starter/internal/api"
	"allaboutapps.dev/aw/go-starter/internal/api/httperrors"
	"allaboutapps.dev/aw/go-starter/internal/models"
	"allaboutapps.dev/aw/go-starter/internal/test"
	"allaboutapps.dev/aw/go-starter/internal/test/fixtures"
	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/require"
	"github.com/volatiletech/null/v8"
	"github.com/volatiletech/sqlboiler/v4/boil"
)

func assertUserAndRelatedData(ctx context.Context, s *api.Server, t *testing.T, userID string, expectExists bool) {
	userExists, err := models.Users(
		models.UserWhere.ID.EQ(userID),
	).Exists(ctx, s.DB)
	require.NoError(t, err)
	require.Equal(t, expectExists, userExists)

	appUserProfileExists, err := models.AppUserProfiles(
		models.AppUserProfileWhere.UserID.EQ(userID),
	).Exists(ctx, s.DB)
	require.NoError(t, err)
	require.Equal(t, expectExists, appUserProfileExists)

	accessTokenExists, err := models.AccessTokens(
		models.AccessTokenWhere.UserID.EQ(userID),
	).Exists(ctx, s.DB)
	require.NoError(t, err)
	require.Equal(t, expectExists, accessTokenExists)

	refreshTokenExists, err := models.RefreshTokens(
		models.RefreshTokenWhere.UserID.EQ(userID),
	).Exists(ctx, s.DB)
	require.NoError(t, err)
	require.Equal(t, expectExists, refreshTokenExists)

	pushTokenExists, err := models.PushTokens(
		models.PushTokenWhere.UserID.EQ(userID),
	).Exists(ctx, s.DB)
	require.NoError(t, err)
	require.Equal(t, expectExists, pushTokenExists)
}

func TestDeleteUserAccount(t *testing.T) {
	test.WithTestServer(t, func(s *api.Server) {
		ctx := context.Background()
		fix := fixtures.Fixtures()

		// expect the user to have a app user profile and different kinds of tokens (access, refresh, push, password reset)
		assertUserAndRelatedData(ctx, s, t, fix.User1.ID, true)

		payload := test.GenericPayload{
			"currentPassword": fixtures.PlainTestUserPassword,
		}

		res := test.PerformRequest(t, s, "DELETE", "/api/v1/auth/account", payload, test.HeadersWithAuth(t, fix.User1AccessToken1.Token))
		require.Equal(t, http.StatusNoContent, res.Result().StatusCode)

		// expect the user and all related data to be deleted
		assertUserAndRelatedData(ctx, s, t, fix.User1.ID, false)
	})
}

func TestDeleteUserAccountCurrentPasswordWrong(t *testing.T) {
	test.WithTestServer(t, func(s *api.Server) {
		fix := fixtures.Fixtures()

		payload := test.GenericPayload{
			"currentPassword": "wrongpassword",
		}

		res := test.PerformRequest(t, s, "DELETE", "/api/v1/auth/account", payload, test.HeadersWithAuth(t, fix.User1AccessToken1.Token))
		test.RequireHTTPError(t, res, httperrors.NewFromEcho(echo.ErrUnauthorized))
	})
}

func TestDeleteUserAccountMissingCurrentPassword(t *testing.T) {
	test.WithTestServer(t, func(s *api.Server) {
		fix := fixtures.Fixtures()

		res := test.PerformRequest(t, s, "DELETE", "/api/v1/auth/account", nil, test.HeadersWithAuth(t, fix.User1AccessToken1.Token))
		require.Equal(t, http.StatusBadRequest, res.Result().StatusCode)
	})
}

func TestDeleteUserAccountNoAuth(t *testing.T) {
	test.WithTestServer(t, func(s *api.Server) {
		res := test.PerformRequest(t, s, "DELETE", "/api/v1/auth/account", nil, nil)
		test.RequireHTTPError(t, res, httperrors.NewFromEcho(echo.ErrUnauthorized))
	})
}

func TestDeleteUserAccountUserNotActive(t *testing.T) {
	test.WithTestServer(t, func(s *api.Server) {
		fix := fixtures.Fixtures()
		ctx := context.Background()

		fix.User1.IsActive = false
		_, err := fix.User1.Update(ctx, s.DB, boil.Whitelist(models.UserColumns.IsActive))
		require.NoError(t, err)

		payload := test.GenericPayload{
			"currentPassword": "somepassword",
		}

		res := test.PerformRequest(t, s, "DELETE", "/api/v1/auth/account", payload, test.HeadersWithAuth(t, fix.User1AccessToken1.Token))
		test.RequireHTTPError(t, res, httperrors.ErrForbiddenUserDeactivated)
	})
}

func TestDeleteUserAccountUserNotLocal(t *testing.T) {
	test.WithTestServer(t, func(s *api.Server) {
		fix := fixtures.Fixtures()
		ctx := context.Background()

		fix.User1.Password = null.String{}
		_, err := fix.User1.Update(ctx, s.DB, boil.Whitelist(models.UserColumns.Password))
		require.NoError(t, err)

		payload := test.GenericPayload{
			"currentPassword": "wrongpassword",
		}

		res := test.PerformRequest(t, s, "DELETE", "/api/v1/auth/account", payload, test.HeadersWithAuth(t, fix.User1AccessToken1.Token))
		test.RequireHTTPError(t, res, httperrors.ErrForbiddenNotLocalUser)
	})
}
