package daos_test

import (
	"testing"

	"github.com/pocketbase/pocketbase/models"
	"github.com/pocketbase/pocketbase/tests"
)

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	expected := "SELECT {{_admins}}.* FROM `_admins`"

	sql := app.Dao().AdminQuery().Build().SQL()
	if sql != expected {
		t.Errorf("Expected sql %s, got %s", expected, sql)
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	scenarios := []struct {
		id          string
		expectError bool
	}{
		{" ", true},
		{"missing", true},
		{"9q2trqumvlyr3bd", false},
	}

	for i, scenario := range scenarios {
		admin, err := app.Dao().FindAdminById(scenario.id)

		hasErr := err != nil
		if hasErr != scenario.expectError {
			t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
		}

		if admin != nil && admin.Id != scenario.id {
			t.Errorf("(%d) Expected admin with id %s, got %s", i, scenario.id, admin.Id)
		}
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	scenarios := []struct {
		email       string
		expectError bool
	}{
		{"", true},
		{"invalid", true},
		{"missing@example.com", true},
		{"test@example.com", false},
	}

	for i, scenario := range scenarios {
		admin, err := app.Dao().FindAdminByEmail(scenario.email)

		hasErr := err != nil
		if hasErr != scenario.expectError {
			t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
			continue
		}

		if !scenario.expectError && admin.Email != scenario.email {
			t.Errorf("(%d) Expected admin with email %s, got %s", i, scenario.email, admin.Email)
		}
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	scenarios := []struct {
		token         string
		baseKey       string
		expectedEmail string
		expectError   bool
	}{
		// invalid auth token
		{
			"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTY0MDk5MTY2MX0.qrbkI2TITtFKMP6vrATrBVKPGjEiDIBeQ0mlqPGMVeY",
			app.Settings().AdminAuthToken.Secret,
			"",
			true,
		},
		// expired token
		{
			"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MTY0MDk5MTY2MX0.I7w8iktkleQvC7_UIRpD7rNzcU4OnF7i7SFIUu6lD_4",
			app.Settings().AdminAuthToken.Secret,
			"",
			true,
		},
		// wrong base token (password reset token secret instead of auth secret)
		{
			"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			app.Settings().AdminPasswordResetToken.Secret,
			"",
			true,
		},
		// valid token
		{
			"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InN5d2JoZWNuaDQ2cmhtMCIsInR5cGUiOiJhZG1pbiIsImV4cCI6MjIwODk4NTI2MX0.M1m--VOqGyv0d23eeUc0r9xE8ZzHaYVmVFw1VZW6gT8",
			app.Settings().AdminAuthToken.Secret,
			"test@example.com",
			false,
		},
	}

	for i, scenario := range scenarios {
		admin, err := app.Dao().FindAdminByToken(scenario.token, scenario.baseKey)

		hasErr := err != nil
		if hasErr != scenario.expectError {
			t.Errorf("(%d) Expected hasErr to be %v, got %v (%v)", i, scenario.expectError, hasErr, err)
			continue
		}

		if !scenario.expectError && admin.Email != scenario.expectedEmail {
			t.Errorf("(%d) Expected admin model %s, got %s", i, scenario.expectedEmail, admin.Email)
		}
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	result1, err := app.Dao().TotalAdmins()
	if err != nil {
		t.Fatal(err)
	}
	if result1 != 3 {
		t.Fatalf("Expected 3 admins, got %d", result1)
	}

	// delete all
	app.Dao().DB().NewQuery("delete from {{_admins}}").Execute()

	result2, err := app.Dao().TotalAdmins()
	if err != nil {
		t.Fatal(err)
	}
	if result2 != 0 {
		t.Fatalf("Expected 0 admins, got %d", result2)
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	scenarios := []struct {
		email     string
		excludeId string
		expected  bool
	}{
		{"", "", false},
		{"test@example.com", "", false},
		{"test2@example.com", "", false},
		{"test3@example.com", "", false},
		{"new@example.com", "", true},
		{"test@example.com", "sywbhecnh46rhm0", true},
	}

	for i, scenario := range scenarios {
		result := app.Dao().IsAdminEmailUnique(scenario.email, scenario.excludeId)
		if result != scenario.expected {
			t.Errorf("(%d) Expected %v, got %v", i, scenario.expected, result)
		}
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	// try to delete unsaved admin model
	deleteErr0 := app.Dao().DeleteAdmin(&models.Admin{})
	if deleteErr0 == nil {
		t.Fatal("Expected error, got nil")
	}

	admin1, err := app.Dao().FindAdminByEmail("test@example.com")
	if err != nil {
		t.Fatal(err)
	}
	admin2, err := app.Dao().FindAdminByEmail("test2@example.com")
	if err != nil {
		t.Fatal(err)
	}
	admin3, err := app.Dao().FindAdminByEmail("test3@example.com")
	if err != nil {
		t.Fatal(err)
	}

	deleteErr1 := app.Dao().DeleteAdmin(admin1)
	if deleteErr1 != nil {
		t.Fatal(deleteErr1)
	}

	deleteErr2 := app.Dao().DeleteAdmin(admin2)
	if deleteErr2 != nil {
		t.Fatal(deleteErr2)
	}

	// cannot delete the only remaining admin
	deleteErr3 := app.Dao().DeleteAdmin(admin3)
	if deleteErr3 == nil {
		t.Fatal("Expected delete error, got nil")
	}

	total, _ := app.Dao().TotalAdmins()
	if total != 1 {
		t.Fatalf("Expected only 1 admin, got %d", total)
	}
}

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

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	// create
	newAdmin := &models.Admin{}
	newAdmin.Email = "new@example.com"
	newAdmin.SetPassword("123456")
	saveErr1 := app.Dao().SaveAdmin(newAdmin)
	if saveErr1 != nil {
		t.Fatal(saveErr1)
	}
	if newAdmin.Id == "" {
		t.Fatal("Expected admin id to be set")
	}

	// update
	existingAdmin, err := app.Dao().FindAdminByEmail("test@example.com")
	if err != nil {
		t.Fatal(err)
	}
	updatedEmail := "test_update@example.com"
	existingAdmin.Email = updatedEmail
	saveErr2 := app.Dao().SaveAdmin(existingAdmin)
	if saveErr2 != nil {
		t.Fatal(saveErr2)
	}
	existingAdmin, _ = app.Dao().FindAdminById(existingAdmin.Id)
	if existingAdmin.Email != updatedEmail {
		t.Fatalf("Expected admin email to be %s, got %s", updatedEmail, existingAdmin.Email)
	}
}