package bolt

import (
	"fmt"
	"log"
	"strings"
	"testing"

	portainer "github.com/portainer/portainer/api"
)

// testVersion is a helper which tests current store version against wanted version
func testVersion(store *Store, versionWant int, t *testing.T) {
	if v, _ := store.version(); v != versionWant {
		t.Errorf("Expect store version to be %d but was %d", versionWant, v)
	}
}

func TestMigrateData(t *testing.T) {
	t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
		store, teardown := MustNewTestStore(false)
		defer teardown()

		if !store.IsNew() {
			t.Error("Expect a new DB")
		}

		store.MigrateData(false)

		testVersion(store, portainer.DBVersion, t)
		store.Close()

		store.Open()
		if store.IsNew() {
			t.Error("Expect store to NOT be new DB")
		}
	})

	tests := []struct {
		version         int
		expectedVersion int
	}{
		{version: 2, expectedVersion: portainer.DBVersion},
		{version: 21, expectedVersion: portainer.DBVersion},
	}
	for _, tc := range tests {
		store, teardown := MustNewTestStore(true)
		defer teardown()

		// Setup data
		store.VersionService.StoreDBVersion(tc.version)

		// Required roles by migrations 22.2
		store.RoleService.CreateRole(&portainer.Role{ID: 1})
		store.RoleService.CreateRole(&portainer.Role{ID: 2})
		store.RoleService.CreateRole(&portainer.Role{ID: 3})
		store.RoleService.CreateRole(&portainer.Role{ID: 4})

		t.Run(fmt.Sprintf("MigrateData for version %d", tc.version), func(t *testing.T) {
			store.MigrateData(true)
			testVersion(store, tc.expectedVersion, t)
		})

		t.Run(fmt.Sprintf("Restoring DB after migrateData for version %d", tc.version), func(t *testing.T) {
			store.Rollback(true)
			store.Open()
			testVersion(store, tc.version, t)
		})
	}

	t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
		store, teardown := MustNewTestStore(false)
		defer teardown()

		version := 2
		store.VersionService.StoreDBVersion(version)

		store.MigrateData(true)

		testVersion(store, version, t)
	})

	t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
		store, teardown := MustNewTestStore(false)
		defer teardown()
		store.VersionService.StoreDBVersion(0)

		store.MigrateData(true)

		options := store.setupOptions(getBackupRestoreOptions(store))

		if !isFileExist(options.BackupPath) {
			t.Errorf("Backup file should exist; file=%s", options.BackupPath)
		}
	})

	t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
		store, teardown := MustNewTestStore(false)
		defer teardown()

		store.VersionService.StoreIsUpdating(true)

		store.MigrateData(true)

		options := store.setupOptions(getBackupRestoreOptions(store))

		if isFileExist(options.BackupPath) {
			t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
		}
	})

	t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
		store, teardown := MustNewTestStore(false)
		defer teardown()

		store.MigrateData(true)

		options := store.setupOptions(getBackupRestoreOptions(store))

		if isFileExist(options.BackupPath) {
			t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
		}
	})

}

func Test_getBackupRestoreOptions(t *testing.T) {
	store, teardown := MustNewTestStore(false)
	defer teardown()

	options := getBackupRestoreOptions(store)

	wantDir := store.commonBackupDir()
	if !strings.HasSuffix(options.BackupDir, wantDir) {
		log.Fatalf("incorrect backup dir; got=%s, want=%s", options.BackupDir, wantDir)
	}

	wantFilename := "portainer.db.bak"
	if options.BackupFileName != wantFilename {
		log.Fatalf("incorrect backup file; got=%s, want=%s", options.BackupFileName, wantFilename)
	}
}

func TestRollback(t *testing.T) {
	t.Run("Rollback should restore upgrade after backup", func(t *testing.T) {
		version := 21
		store, teardown := MustNewTestStore(false)
		defer teardown()
		store.VersionService.StoreDBVersion(version)

		_, err := store.BackupWithOptions(getBackupRestoreOptions(store))
		if err != nil {
			log.Fatal(err)
		}

		// Change the current edition
		err = store.VersionService.StoreDBVersion(version + 10)
		if err != nil {
			log.Fatal(err)
		}

		err = store.Rollback(true)
		if err != nil {
			t.Logf("Rollback failed: %s", err)
			t.Fail()
			return
		}

		store.Open()
		testVersion(store, version, t)
	})
}