package metastore

import (
	"errors"
	"testing"
	"time"

	"warpbox/lib/config"
)

func TestOpenClose(t *testing.T) {
	store, err := Open(t.TempDir())
	if err != nil {
		t.Fatalf("Open returned error: %v", err)
	}
	if err := store.Close(); err != nil {
		t.Fatalf("Close returned error: %v", err)
	}
}

func TestBootstrapAdminFromPassword(t *testing.T) {
	clearMetastoreConfigEnv(t)
	t.Setenv("WARPBOX_ADMIN_PASSWORD", "secret-pass")
	t.Setenv("WARPBOX_ADMIN_EMAIL", "admin@example.test")

	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("Load returned error: %v", err)
	}
	store := openTestStore(t)

	result, err := BootstrapAdmin(cfg, store)
	if err != nil {
		t.Fatalf("BootstrapAdmin returned error: %v", err)
	}
	if !result.AdminLoginEnabled {
		t.Fatal("expected admin login to be enabled")
	}
	if !result.AdminTag.Protected {
		t.Fatal("expected admin tag to be protected")
	}
	if result.AdminUser == nil {
		t.Fatal("expected bootstrap admin user")
	}
	if !hasString(result.AdminUser.TagIDs, result.AdminTag.ID) {
		t.Fatal("expected bootstrap admin to have admin tag")
	}
	if !VerifyPassword(result.AdminUser.PasswordHash, "secret-pass") {
		t.Fatal("expected bootstrap admin password to verify")
	}
}

func TestBootstrapAdminDisabledWithoutPassword(t *testing.T) {
	clearMetastoreConfigEnv(t)

	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("Load returned error: %v", err)
	}
	store := openTestStore(t)

	result, err := BootstrapAdmin(cfg, store)
	if err != nil {
		t.Fatalf("BootstrapAdmin returned error: %v", err)
	}
	if result.AdminLoginEnabled {
		t.Fatal("expected admin login to be disabled without password or existing admin")
	}
	if !result.AdminTag.Protected {
		t.Fatal("expected admin tag to still be created")
	}
	users, err := store.ListUsers()
	if err != nil {
		t.Fatalf("ListUsers returned error: %v", err)
	}
	if len(users) != 0 {
		t.Fatalf("expected no users, got %d", len(users))
	}
}

func TestDuplicateUsersAndTags(t *testing.T) {
	store := openTestStore(t)

	if _, err := store.CreateUserWithPassword("alex", "alex@example.test", "secret", nil); err != nil {
		t.Fatalf("CreateUserWithPassword returned error: %v", err)
	}
	if _, err := store.CreateUserWithPassword("Alex", "other@example.test", "secret", nil); !errors.Is(err, ErrDuplicate) {
		t.Fatalf("expected duplicate username error, got %v", err)
	}
	if _, err := store.CreateUserWithPassword("other", "alex@example.test", "secret", nil); !errors.Is(err, ErrDuplicate) {
		t.Fatalf("expected duplicate email error, got %v", err)
	}

	tag := Tag{Name: "staff"}
	if err := store.CreateTag(&tag); err != nil {
		t.Fatalf("CreateTag returned error: %v", err)
	}
	duplicate := Tag{Name: "Staff"}
	if err := store.CreateTag(&duplicate); !errors.Is(err, ErrDuplicate) {
		t.Fatalf("expected duplicate tag error, got %v", err)
	}
}

func TestPermissionResolutionAndGlobalCaps(t *testing.T) {
	clearMetastoreConfigEnv(t)
	t.Setenv("WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES", "50")
	t.Setenv("WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES", "100")
	t.Setenv("WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES", "1000")

	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("Load returned error: %v", err)
	}
	tagFileLimit := int64(80)
	tagBoxLimit := int64(2000)
	userFileLimit := int64(60)
	user := User{MaxFileSizeBytes: &userFileLimit}
	tags := []Tag{
		{
			Permissions: TagPermissions{
				UploadAllowed:        true,
				AllowedExpirySeconds: []int64{3600, 600},
				MaxFileSizeBytes:     &tagFileLimit,
				MaxBoxSizeBytes:      &tagBoxLimit,
				ZipDownloadAllowed:   true,
			},
		},
	}

	perms := ResolveUserPermissions(cfg, user, tags)
	if !perms.UploadAllowed || !perms.ZipDownloadAllowed {
		t.Fatal("expected tag booleans to grant permissions")
	}
	if perms.MaxFileSizeBytes != 80 {
		t.Fatalf("expected tag limit to beat user/default limit, got %d", perms.MaxFileSizeBytes)
	}
	if perms.MaxBoxSizeBytes != 1000 {
		t.Fatalf("expected global max box cap, got %d", perms.MaxBoxSizeBytes)
	}
	if len(perms.AllowedExpirySeconds) != 2 || perms.AllowedExpirySeconds[0] != 600 || perms.AllowedExpirySeconds[1] != 3600 {
		t.Fatalf("unexpected expiry durations: %#v", perms.AllowedExpirySeconds)
	}
}

func TestSettingsStorageAndPrecedence(t *testing.T) {
	clearMetastoreConfigEnv(t)
	t.Setenv("WARPBOX_API_ENABLED", "true")

	store := openTestStore(t)
	if err := store.SetSetting(config.SettingAPIEnabled, "false"); err != nil {
		t.Fatalf("SetSetting returned error: %v", err)
	}
	overrides, err := store.ListSettings()
	if err != nil {
		t.Fatalf("ListSettings returned error: %v", err)
	}
	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("Load returned error: %v", err)
	}
	if err := cfg.ApplyOverrides(overrides); err != nil {
		t.Fatalf("ApplyOverrides returned error: %v", err)
	}
	if cfg.APIEnabled {
		t.Fatal("expected stored DB override to beat env")
	}
}

func TestSessionExpiry(t *testing.T) {
	store := openTestStore(t)
	session, err := store.CreateSession("user-id", time.Millisecond)
	if err != nil {
		t.Fatalf("CreateSession returned error: %v", err)
	}
	time.Sleep(2 * time.Millisecond)
	if _, ok, err := store.GetSession(session.Token); err != nil || ok {
		t.Fatalf("expected expired session to be invalid, ok=%v err=%v", ok, err)
	}
}

func openTestStore(t *testing.T) *Store {
	t.Helper()
	store, err := Open(t.TempDir())
	if err != nil {
		t.Fatalf("Open returned error: %v", err)
	}
	t.Cleanup(func() {
		_ = store.Close()
	})
	return store
}

func clearMetastoreConfigEnv(t *testing.T) {
	t.Helper()
	for _, name := range []string{
		"WARPBOX_DATA_DIR",
		"WARPBOX_ADMIN_PASSWORD",
		"WARPBOX_ADMIN_USERNAME",
		"WARPBOX_ADMIN_EMAIL",
		"WARPBOX_ADMIN_ENABLED",
		"WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE",
		"WARPBOX_ADMIN_COOKIE_SECURE",
		"WARPBOX_GUEST_UPLOADS_ENABLED",
		"WARPBOX_API_ENABLED",
		"WARPBOX_ZIP_DOWNLOADS_ENABLED",
		"WARPBOX_ONE_TIME_DOWNLOADS_ENABLED",
		"WARPBOX_RENEW_ON_ACCESS_ENABLED",
		"WARPBOX_RENEW_ON_DOWNLOAD_ENABLED",
		"WARPBOX_DEFAULT_GUEST_EXPIRY_SECONDS",
		"WARPBOX_MAX_GUEST_EXPIRY_SECONDS",
		"WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES",
		"WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES",
		"WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES",
		"WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES",
		"WARPBOX_SESSION_TTL_SECONDS",
		"WARPBOX_BOX_POLL_INTERVAL_MS",
		"WARPBOX_THUMBNAIL_BATCH_SIZE",
		"WARPBOX_THUMBNAIL_INTERVAL_SECONDS",
	} {
		t.Setenv(name, "")
	}
}
