package store

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"github.com/oklog/ulid/v2"
)

func newSetupTokenTestStore(t *testing.T) (*Store, string, string) {
	t.Helper()
	st, err := Open(context.Background(), filepath.Join(t.TempDir(), "rm.db"))
	if err != nil {
		t.Fatalf("open: %v", err)
	}
	t.Cleanup(func() { _ = st.Close() })
	uid := ulid.Make().String()
	creator := ulid.Make().String()
	now := time.Now().UTC()
	if err := st.CreateUser(context.Background(), User{
		ID: creator, Username: "creator", PasswordHash: "x",
		Role: RoleAdmin, CreatedAt: now,
	}); err != nil {
		t.Fatalf("create creator: %v", err)
	}
	if err := st.CreateUser(context.Background(), User{
		ID: uid, Username: "target", PasswordHash: "",
		Role: RoleOperator, CreatedAt: now, MustChangePassword: true,
	}); err != nil {
		t.Fatalf("create target: %v", err)
	}
	return st, uid, creator
}

func TestSetupTokenSetAndLookup(t *testing.T) {
	t.Parallel()
	st, uid, creator := newSetupTokenTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()

	if err := st.SetSetupToken(ctx, SetupToken{
		UserID: uid, TokenHash: "abc123",
		ExpiresAt: now.Add(time.Hour),
		CreatedAt: now, CreatedBy: &creator,
	}); err != nil {
		t.Fatalf("set: %v", err)
	}
	got, err := st.LookupSetupToken(ctx, "abc123")
	if err != nil {
		t.Fatalf("lookup: %v", err)
	}
	if got.UserID != uid {
		t.Errorf("user_id: got %q want %q", got.UserID, uid)
	}
}

func TestSetupTokenReplaces(t *testing.T) {
	t.Parallel()
	st, uid, creator := newSetupTokenTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()

	_ = st.SetSetupToken(ctx, SetupToken{
		UserID: uid, TokenHash: "old",
		ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator,
	})
	_ = st.SetSetupToken(ctx, SetupToken{
		UserID: uid, TokenHash: "new",
		ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator,
	})
	if _, err := st.LookupSetupToken(ctx, "old"); err == nil {
		t.Error("old token should be gone")
	}
	if _, err := st.LookupSetupToken(ctx, "new"); err != nil {
		t.Errorf("new token should resolve: %v", err)
	}
}

func TestSetupTokenDelete(t *testing.T) {
	t.Parallel()
	st, uid, creator := newSetupTokenTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()

	_ = st.SetSetupToken(ctx, SetupToken{
		UserID: uid, TokenHash: "tk",
		ExpiresAt: now.Add(time.Hour), CreatedAt: now, CreatedBy: &creator,
	})
	if err := st.DeleteSetupToken(ctx, uid); err != nil {
		t.Fatalf("delete: %v", err)
	}
	if _, err := st.LookupSetupToken(ctx, "tk"); err == nil {
		t.Error("deleted token should not resolve")
	}
}

func TestSetupTokenCleanupExpired(t *testing.T) {
	t.Parallel()
	st, uid, creator := newSetupTokenTestStore(t)
	ctx := context.Background()
	now := time.Now().UTC()

	_ = st.SetSetupToken(ctx, SetupToken{
		UserID: uid, TokenHash: "stale",
		ExpiresAt: now.Add(-time.Hour), CreatedAt: now.Add(-2 * time.Hour),
		CreatedBy: &creator,
	})
	n, err := st.CleanupExpiredSetupTokens(ctx, now)
	if err != nil {
		t.Fatalf("cleanup: %v", err)
	}
	if n != 1 {
		t.Errorf("cleanup count: got %d want 1", n)
	}
	if _, err := st.LookupSetupToken(ctx, "stale"); err == nil {
		t.Error("stale token should be gone")
	}
}
