package apis_test

import (
	"archive/zip"
	"bytes"
	"context"
	"io"
	"mime/multipart"
	"net/http"
	"strings"
	"testing"

	"github.com/labstack/echo/v5"
	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/tests"
	"gocloud.dev/blob"
)

func TestBackupsList(t *testing.T) {
	t.Parallel()

	scenarios := []tests.ApiScenario{
		{
			Name:   "unauthorized",
			Method: http.MethodGet,
			Url:    "/api/backups",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as auth record",
			Method: http.MethodGet,
			Url:    "/api/backups",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (empty list)",
			Method: http.MethodGet,
			Url:    "/api/backups",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			ExpectedStatus: 200,
			ExpectedContent: []string{
				`[]`,
			},
		},
		{
			Name:   "authorized as admin",
			Method: http.MethodGet,
			Url:    "/api/backups",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus: 200,
			ExpectedContent: []string{
				`"test1.zip"`,
				`"test2.zip"`,
				`"test3.zip"`,
			},
		},
	}

	for _, scenario := range scenarios {
		scenario.Test(t)
	}
}

func TestBackupsCreate(t *testing.T) {
	t.Parallel()

	scenarios := []tests.ApiScenario{
		{
			Name:   "unauthorized",
			Method: http.MethodPost,
			Url:    "/api/backups",
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as auth record",
			Method: http.MethodPost,
			Url:    "/api/backups",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (pending backup)",
			Method: http.MethodPost,
			Url:    "/api/backups",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				app.Store().Set(core.StoreKeyActiveBackup, "")
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (autogenerated name)",
			Method: http.MethodPost,
			Url:    "/api/backups",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				files, err := getBackupFiles(app)
				if err != nil {
					t.Fatal(err)
				}

				if total := len(files); total != 1 {
					t.Fatalf("Expected 1 backup file, got %d", total)
				}

				expected := "pb_backup_"
				if !strings.HasPrefix(files[0].Key, expected) {
					t.Fatalf("Expected backup file with prefix %q, got %q", expected, files[0].Key)
				}
			},
			ExpectedStatus: 204,
		},
		{
			Name:   "authorized as admin (invalid name)",
			Method: http.MethodPost,
			Url:    "/api/backups",
			Body:   strings.NewReader(`{"name":"!test.zip"}`),
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus: 400,
			ExpectedContent: []string{
				`"data":{`,
				`"name":{"code":"validation_match_invalid"`,
			},
		},
		{
			Name:   "authorized as admin (valid name)",
			Method: http.MethodPost,
			Url:    "/api/backups",
			Body:   strings.NewReader(`{"name":"test.zip"}`),
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				files, err := getBackupFiles(app)
				if err != nil {
					t.Fatal(err)
				}

				if total := len(files); total != 1 {
					t.Fatalf("Expected 1 backup file, got %d", total)
				}

				expected := "test.zip"
				if files[0].Key != expected {
					t.Fatalf("Expected backup file %q, got %q", expected, files[0].Key)
				}
			},
			ExpectedStatus: 204,
		},
	}

	for _, scenario := range scenarios {
		scenario.Test(t)
	}
}

func TestBackupsUpload(t *testing.T) {
	t.Parallel()

	// create dummy form data bodies
	type body struct {
		buffer      io.Reader
		contentType string
	}
	bodies := make([]body, 10)
	for i := 0; i < 10; i++ {
		func() {
			zb := new(bytes.Buffer)
			zw := zip.NewWriter(zb)
			if err := zw.Close(); err != nil {
				t.Fatal(err)
			}

			b := new(bytes.Buffer)
			mw := multipart.NewWriter(b)

			mfw, err := mw.CreateFormFile("file", "test")
			if err != nil {
				t.Fatal(err)
			}
			if _, err := io.Copy(mfw, zb); err != nil {
				t.Fatal(err)
			}

			mw.Close()

			bodies[i] = body{
				buffer:      b,
				contentType: mw.FormDataContentType(),
			}
		}()
	}
	// ---

	scenarios := []tests.ApiScenario{
		{
			Name:   "unauthorized",
			Method: http.MethodPost,
			Url:    "/api/backups/upload",
			Body:   bodies[0].buffer,
			RequestHeaders: map[string]string{
				"Content-Type": bodies[0].contentType,
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as auth record",
			Method: http.MethodPost,
			Url:    "/api/backups/upload",
			Body:   bodies[1].buffer,
			RequestHeaders: map[string]string{
				"Content-Type":  bodies[1].contentType,
				"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (missing file)",
			Method: http.MethodPost,
			Url:    "/api/backups/upload",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				ensureNoBackups(t, app)
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{`},
		},
		{
			Name:   "authorized as admin (existing backup name)",
			Method: http.MethodPost,
			Url:    "/api/backups/upload",
			Body:   bodies[3].buffer,
			RequestHeaders: map[string]string{
				"Content-Type":  bodies[3].contentType,
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				fsys, err := app.NewBackupsFilesystem()
				if err != nil {
					t.Fatal(err)
				}
				defer fsys.Close()
				// create a dummy backup file to simulate existing backups
				if err := fsys.Upload([]byte("123"), "test"); err != nil {
					t.Fatal(err)
				}
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				files, _ := getBackupFiles(app)
				if total := len(files); total != 1 {
					t.Fatalf("Expected %d backup file, got %d", 1, total)
				}
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{"file":{`},
		},
		{
			Name:   "authorized as admin (valid file)",
			Method: http.MethodPost,
			Url:    "/api/backups/upload",
			Body:   bodies[4].buffer,
			RequestHeaders: map[string]string{
				"Content-Type":  bodies[4].contentType,
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				files, _ := getBackupFiles(app)
				if total := len(files); total != 1 {
					t.Fatalf("Expected %d backup file, got %d", 1, total)
				}
			},
			ExpectedStatus: 204,
		},
	}

	for _, scenario := range scenarios {
		scenario.Test(t)
	}
}

func TestBackupsDownload(t *testing.T) {
	t.Parallel()

	scenarios := []tests.ApiScenario{
		{
			Name:   "unauthorized",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with record auth header",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with admin auth header",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with empty or invalid token",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip?token=",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with valid record auth token",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip?token=eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with valid record file token",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsImV4cCI6MTg5MzQ1MjQ2MSwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwidHlwZSI6ImF1dGhSZWNvcmQifQ.0d_0EO6kfn9ijZIQWAqgRi8Bo1z7MKcg1LQpXhQsEPk",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with valid admin auth token",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with expired admin file token",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsImV4cCI6MTY0MDk5MTY2MSwidHlwZSI6ImFkbWluIn0.g7Q_3UX6H--JWJ7yt1Hoe-1ugTX1KpbKzdt0zjGSe-E",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  403,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with valid admin file token but missing backup name",
			Method: http.MethodGet,
			Url:    "/api/backups/missing?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsImV4cCI6MTg5MzQ1MjQ2MSwidHlwZSI6ImFkbWluIn0.LyAMpSfaHVsuUqIlqqEbhDQSdFzoPz_EIDcb2VJMBsU",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "with valid admin file token",
			Method: http.MethodGet,
			Url:    "/api/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsImV4cCI6MTg5MzQ1MjQ2MSwidHlwZSI6ImFkbWluIn0.LyAMpSfaHVsuUqIlqqEbhDQSdFzoPz_EIDcb2VJMBsU",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus: 200,
			ExpectedContent: []string{
				`storage/`,
				`data.db`,
				`logs.db`,
			},
		},
		{
			Name:   "with valid admin file token and backup name with escaped char",
			Method: http.MethodGet,
			Url:    "/api/backups/%40test4.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsImV4cCI6MTg5MzQ1MjQ2MSwidHlwZSI6ImFkbWluIn0.LyAMpSfaHVsuUqIlqqEbhDQSdFzoPz_EIDcb2VJMBsU",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus: 200,
			ExpectedContent: []string{
				`storage/`,
				`data.db`,
				`logs.db`,
			},
		},
	}

	for _, scenario := range scenarios {
		scenario.Test(t)
	}
}

func TestBackupsDelete(t *testing.T) {
	t.Parallel()

	noTestBackupFilesChanges := func(t *testing.T, app *tests.TestApp) {
		files, err := getBackupFiles(app)
		if err != nil {
			t.Fatal(err)
		}

		expected := 4
		if total := len(files); total != expected {
			t.Fatalf("Expected %d backup(s), got %d", expected, total)
		}
	}

	scenarios := []tests.ApiScenario{
		{
			Name:   "unauthorized",
			Method: http.MethodDelete,
			Url:    "/api/backups/test1.zip",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				noTestBackupFilesChanges(t, app)
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as auth record",
			Method: http.MethodDelete,
			Url:    "/api/backups/test1.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				noTestBackupFilesChanges(t, app)
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (missing file)",
			Method: http.MethodDelete,
			Url:    "/api/backups/missing.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				noTestBackupFilesChanges(t, app)
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (existing file with matching active backup)",
			Method: http.MethodDelete,
			Url:    "/api/backups/test1.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}

				// mock active backup with the same name to delete
				app.Store().Set(core.StoreKeyActiveBackup, "test1.zip")
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				noTestBackupFilesChanges(t, app)
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (existing file and no matching active backup)",
			Method: http.MethodDelete,
			Url:    "/api/backups/test1.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}

				// mock active backup with different name
				app.Store().Set(core.StoreKeyActiveBackup, "new.zip")
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				files, err := getBackupFiles(app)
				if err != nil {
					t.Fatal(err)
				}

				if total := len(files); total != 3 {
					t.Fatalf("Expected %d backup files, got %d", 3, total)
				}

				deletedFile := "test1.zip"

				for _, f := range files {
					if f.Key == deletedFile {
						t.Fatalf("Expected backup %q to be deleted", deletedFile)
					}
				}
			},
			ExpectedStatus: 204,
		},
		{
			Name:   "authorized as admin (backup with escaped character)",
			Method: http.MethodDelete,
			Url:    "/api/backups/%40test4.zip",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			AfterTestFunc: func(t *testing.T, app *tests.TestApp, res *http.Response) {
				files, err := getBackupFiles(app)
				if err != nil {
					t.Fatal(err)
				}

				if total := len(files); total != 3 {
					t.Fatalf("Expected %d backup files, got %d", 3, total)
				}

				deletedFile := "@test4.zip"

				for _, f := range files {
					if f.Key == deletedFile {
						t.Fatalf("Expected backup %q to be deleted", deletedFile)
					}
				}
			},
			ExpectedStatus: 204,
		},
	}

	for _, scenario := range scenarios {
		scenario.Test(t)
	}
}

func TestBackupsRestore(t *testing.T) {
	t.Parallel()

	scenarios := []tests.ApiScenario{
		{
			Name:   "unauthorized",
			Method: http.MethodPost,
			Url:    "/api/backups/test1.zip/restore",
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as auth record",
			Method: http.MethodPost,
			Url:    "/api/backups/test1.zip/restore",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjRxMXhsY2xtZmxva3UzMyIsInR5cGUiOiJhdXRoUmVjb3JkIiwiY29sbGVjdGlvbklkIjoiX3BiX3VzZXJzX2F1dGhfIiwiZXhwIjoyMjA4OTg1MjYxfQ.UwD8JvkbQtXpymT09d7J6fdA0aP9g4FJ1GPh_ggEkzc",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  401,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (missing file)",
			Method: http.MethodPost,
			Url:    "/api/backups/missing.zip/restore",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{}`},
		},
		{
			Name:   "authorized as admin (active backup process)",
			Method: http.MethodPost,
			Url:    "/api/backups/test1.zip/restore",
			RequestHeaders: map[string]string{
				"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			},
			BeforeTestFunc: func(t *testing.T, app *tests.TestApp, e *echo.Echo) {
				if err := createTestBackups(app); err != nil {
					t.Fatal(err)
				}

				app.Store().Set(core.StoreKeyActiveBackup, "")
			},
			ExpectedStatus:  400,
			ExpectedContent: []string{`"data":{}`},
		},
	}

	for _, scenario := range scenarios {
		scenario.Test(t)
	}
}

// -------------------------------------------------------------------

func createTestBackups(app core.App) error {
	ctx := context.Background()

	if err := app.CreateBackup(ctx, "test1.zip"); err != nil {
		return err
	}

	if err := app.CreateBackup(ctx, "test2.zip"); err != nil {
		return err
	}

	if err := app.CreateBackup(ctx, "test3.zip"); err != nil {
		return err
	}

	if err := app.CreateBackup(ctx, "@test4.zip"); err != nil {
		return err
	}

	return nil
}

func getBackupFiles(app core.App) ([]*blob.ListObject, error) {
	fsys, err := app.NewBackupsFilesystem()
	if err != nil {
		return nil, err
	}
	defer fsys.Close()

	return fsys.List("")
}

func ensureNoBackups(t *testing.T, app *tests.TestApp) {
	files, err := getBackupFiles(app)
	if err != nil {
		t.Fatal(err)
	}

	if total := len(files); total != 0 {
		t.Fatalf("Expected 0 backup files, got %d", total)
	}
}