package main

import (
	"bookhoard/internal/config"
	"bookhoard/internal/database"
	"bookhoard/internal/handlers"
	"bookhoard/internal/middleware"
	ratelimit "bookhoard/internal/middleware"
	"bookhoard/internal/router"
	"bookhoard/internal/services"
	wsync "bookhoard/internal/sync"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/labstack/echo/v5"
	echomiddleware "github.com/labstack/echo/v5/middleware"
	"github.com/stretchr/testify/require"
)

// CustomValidator wraps the go-playground validator
type CustomValidator struct {
	validator *validator.Validate
}

func (cv *CustomValidator) Validate(i interface{}) error {
	return cv.validator.Struct(i)
}

// TestDeviceSetup provides a complete, isolated test environment for device tests
type TestDeviceSetup struct {
	Server    *httptest.Server
	DB        *database.Queries
	Config    *config.Config
	User      UserTestData
	Device    DeviceTestData
	Library   LibraryTestData
	UserToken string
}

type LibraryTestData struct {
	ID   string
	Name string
	Type string
}

type UserTestData struct {
	ID       uuid.UUID
	Email    string
	Username string
	Password string
	Token    string
}

type DeviceTestData struct {
	ID         uuid.UUID
	Name       string
	Type       string
	Identifier string
	AuthToken  string
	PGType     database.Devices
}

// TestServerSetup manages the lifecycle of a test server with proper resource cleanup
type TestServerSetup struct {
	Server          *httptest.Server
	DB              *database.Queries
	DBPool          *pgxpool.Pool
	Config          *config.Config
	ConnManager     *wsync.ConnectionManager
	QueueProcessor  *wsync.SyncQueueProcessor
	ProgressService *wsync.ProgressService
	AnnotationService *wsync.AnnotationService
	CleanupCancel   context.CancelFunc
	QueueCtx        context.Context
	QueueCancel     context.CancelFunc
	Token           string
	RegularToken    string
	mu              sync.Mutex
	closed          bool
}

// Close cleans up all resources in the correct order
func (s *TestServerSetup) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return nil
	}

	// Stop queue processor first
	if s.QueueCancel != nil {
		s.QueueCancel()
		s.QueueCancel = nil
	}

	// Reset global worker instance
	services.WorkerInstance = nil

	// Stop connection manager cleanup task
	if s.CleanupCancel != nil {
		s.CleanupCancel()
		s.CleanupCancel = nil
	}

	// Close HTTP server
	if s.Server != nil {
		s.Server.Close()
		s.Server = nil
	}

	// Close database pool (this waits for all connections to be released)
	if s.DBPool != nil {
		s.DBPool.Close()
		s.DBPool = nil
	}

	s.closed = true
	return nil
}

// Helper functions for testing
func containsPrefix(s, prefix string) bool {
	return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}

func contains(s, substr string) bool {
	return strings.Contains(s, substr)
}

func trimSpace(s string) string {
	return strings.TrimSpace(s)
}

// isRunningInContainer detects if tests are running inside a Docker container
func isRunningInContainer() bool {
	// Check for container-specific marker file
	if _, err := os.Stat("/.dockerenv"); err == nil {
		return true
	}

	// Check if /app/uploads exists (container path)
	if _, err := os.Stat("/app/uploads"); err == nil {
		return true
	}

	// Check environment variable (explicit override)
	if os.Getenv("TEST_IN_CONTAINER") == "true" {
		return true
	}

	return false
}

// getUploadPath returns the appropriate upload path based on runtime environment
func getUploadPath() string {
	// Check for explicit override first
	if path := os.Getenv("TEST_UPLOAD_PATH"); path != "" {
		return path
	}

	if isRunningInContainer() {
		return "/app/uploads" // Container path (right side of volume mount)
	}

	return "./uploads" // Host path (left side of volume mount)
}

// getCachePath returns the appropriate cache path based on runtime environment
func getCachePath() string {
	// Check for explicit override first
	if path := os.Getenv("TEST_CACHE_PATH"); path != "" {
		return path
	}

	if isRunningInContainer() {
		return "/app/cache/kepub" // Container path (volume mount)
	}

	// Note: This is a Docker volume on host, not a folder
	// Tests using this should handle the volume appropriately
	return "/app/cache/kepub"
}

// setupDeviceTest creates a complete test environment for device tests
func setupDeviceTest(t *testing.T) *TestDeviceSetup {
	serverSetup := setupTestServer(t)

	// Create user ONCE with known credentials
	user := createTestUserOnce(t, serverSetup.DB)

	// Login to get token
	token := loginUserWithCredentials(t, serverSetup.Server, user.Email, user.Password)

	return &TestDeviceSetup{
		Server:    serverSetup.Server,
		DB:        serverSetup.DB,
		Config:    serverSetup.Config,
		User:      user,
		UserToken: token,
	}
}

// createTestUserOnce returns the pre-created test user info
func createTestUserOnce(t *testing.T, db *database.Queries) UserTestData {
	ctx := context.Background()
	user, err := db.GetUserByEmail(ctx, "testuser@tests.bookhoard.internal")
	require.NoError(t, err, "Test user should exist (created by setupTestServer)")
	userUUID, err := uuid.FromBytes(user.ID.Bytes[0:16])
	require.NoError(t, err, "Should parse user UUID")
	return UserTestData{
		ID:       userUUID,
		Email:    "testuser@tests.bookhoard.internal",
		Username: "testuser",
		Password: "Test@Pass123!",
	}
}

// createRegularUserOnce creates a regular (non-admin) test user with unique credentials
func createRegularUserOnce(t *testing.T, db *database.Queries) UserTestData {
	ctx := context.Background()
	uniqueID := uuid.New().String()[:8]
	email := fmt.Sprintf("regularuser-%s@tests.bookhoard.internal", uniqueID)
	username := fmt.Sprintf("regularuser-%s", uniqueID)

	passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq"
	newUser, err := db.CreateUser(ctx, database.CreateUserParams{
		Email:        email,
		Username:     username,
		PasswordHash: passwordHash,
		FirstName:    pgtype.Text{String: "Regular", Valid: true},
		LastName:     pgtype.Text{String: "User", Valid: true},
		Role:         "user",
	})
	require.NoError(t, err, "Should create regular test user")

	userUUID, err := uuid.FromBytes(newUser.ID.Bytes[0:16])
	require.NoError(t, err, "Should parse user UUID")

	createDefaultCollectionsForUser(t, db, pgtype.UUID{Bytes: userUUID, Valid: true})

	t.Cleanup(func() {
		ctx := context.Background()
		db.DeleteUser(ctx, pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true})
	})

	return UserTestData{
		ID:       userUUID,
		Email:    email,
		Username: username,
		Password: "Test@Pass123!",
	}
}

// uuidToPGType converts uuid.UUID to pgtype.UUID
func uuidToPGType(u uuid.UUID) pgtype.UUID {
	return pgtype.UUID{Bytes: [16]byte(u), Valid: true}
}

// createDefaultCollectionsForUser creates the 4 default system collections for a user
func createDefaultCollectionsForUser(t *testing.T, db *database.Queries, userID pgtype.UUID) {
	ctx := context.Background()
	defaultCollections := []struct {
		Name        string
		Description string
		Icon        string
		Color       string
		QueryType   string
		Priority    int32
	}{
		{"continue-reading", "Books you're currently reading (0 < progress < 1)", "📖", "#7aa2f7", "continue-reading", 1},
		{"recently-added", "Newly added items to this library", "🆕", "#9ece6a", "recently-added", 2},
		{"recently-read", "Books you've finished (progress >= 1)", "✅", "#e0af68", "recently-read", 3},
		{"not-started", "Books you haven't read yet (progress = 0 or no record)", "📕", "#f7768e", "not-started", 4},
		{"continue-series", "Next book in series you're reading", "📚", "#bb9af7", "continue-series", 5},
	}

	for _, col := range defaultCollections {
		_, err := db.CreateSystemCollection(ctx, database.CreateSystemCollectionParams{
			UserID:          userID,
			Name:            col.Name,
			Description:     pgtype.Text{String: col.Description, Valid: true},
			Icon:            pgtype.Text{String: col.Icon, Valid: true},
			Color:           pgtype.Text{String: col.Color, Valid: true},
			ShowOnDashboard: pgtype.Bool{Bool: true, Valid: true},
			QueryType:       pgtype.Text{String: col.QueryType, Valid: true},
			Priority:        pgtype.Int4{Int32: col.Priority, Valid: true},
		})
		require.NoError(t, err, "Should create default collection: "+col.Name)
	}
}

// loginUserWithCredentials performs explicit login with provided credentials
func loginUserWithCredentials(t *testing.T, ts *httptest.Server, email, password string) string {
	loginRequest := map[string]interface{}{
		"login":    email,
		"password": password,
	}
	body, _ := json.Marshal(loginRequest)

	req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err, "Failed to login")
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusOK, resp.StatusCode, "Login should succeed")

	var result map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(t, err)

	token, ok := result["access_token"].(string)
	require.True(t, ok, "Should have access_token")
	require.NotEmpty(t, token, "Access token should not be empty")

	return token
}

// CreateDevice creates a test device for the TestDeviceSetup
func (s *TestDeviceSetup) CreateDevice(t *testing.T, deviceName, deviceType, deviceIdentifier string) *DeviceTestData {
	deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())

	pgUserID := pgtype.UUID{Bytes: [16]byte(s.User.ID), Valid: true}
	device, err := s.DB.CreateDevice(context.Background(), database.CreateDeviceParams{
		UserID:               pgUserID,
		DeviceName:           deviceName,
		DeviceType:           deviceType,
		DeviceIdentifier:     deviceIdentifier,
		AuthToken:            deviceToken,
		SyncEnabled:          pgtype.Bool{Bool: true, Valid: true},
		AutoSync:             pgtype.Bool{Bool: true, Valid: true},
		SyncFrequencyMinutes: pgtype.Int4{Int32: 5, Valid: true},
		DeviceMetadata:       []byte("{}"),
	})
	require.NoError(t, err, "Should create device")

	deviceUUID, err := uuid.FromBytes(device.ID.Bytes[0:16])
	require.NoError(t, err, "Should parse device ID")

	return &DeviceTestData{
		ID:         deviceUUID,
		Name:       deviceName,
		Type:       deviceType,
		Identifier: deviceIdentifier,
		AuthToken:  deviceToken,
		PGType:     device,
	}
}

// CreateLibrary creates a test library for the TestDeviceSetup
func (s *TestDeviceSetup) CreateLibrary(t *testing.T, name, libraryType string) string {
	ctx := context.Background()

	// Get the library_type_id for the specified type
	libraryTypeRow, err := s.DB.GetLibraryTypeByName(ctx, libraryType)
	require.NoError(t, err, "Should find library type")

	pgUserID := pgtype.UUID{Bytes: [16]byte(s.User.ID), Valid: true}
	library, err := s.DB.CreateLibrary(ctx, database.CreateLibraryParams{
		Name:             name,
		Description:      pgtype.Text{String: "Test library description", Valid: true},
		LibraryTypeID:    libraryTypeRow.ID,
		CreatedByAdminID: pgUserID,
	})
	require.NoError(t, err, "Should create library")

	libraryUUID, err := uuid.FromBytes(library.ID.Bytes[0:16])
	require.NoError(t, err, "Should parse library ID")

	s.Library = LibraryTestData{
		ID:   libraryUUID.String(),
		Name: name,
		Type: libraryType,
	}

	return libraryUUID.String()
}

// CreateCollection creates a test collection for the TestDeviceSetup
func (s *TestDeviceSetup) CreateCollection(t *testing.T, name string) string {
	ctx := context.Background()

	pgUserID := pgtype.UUID{Bytes: [16]byte(s.User.ID), Valid: true}
	collection, err := s.DB.CreateCollection(ctx, database.CreateCollectionParams{
		UserID:      pgUserID,
		Name:        name,
		Description: pgtype.Text{String: "Test collection description", Valid: true},
		Color:       pgtype.Text{String: "#FF5733", Valid: true},
		Icon:        pgtype.Text{String: "folder", Valid: true},
	})
	require.NoError(t, err, "Should create collection")

	collectionUUID, err := uuid.FromBytes(collection.ID.Bytes[0:16])
	require.NoError(t, err, "Should parse collection ID")

	return collectionUUID.String()
}

// setupTestServer creates a test server with a test database
// Returns: *TestServerSetup with automatic cleanup via t.Cleanup
func setupTestServer(t *testing.T) *TestServerSetup {
	// Load configuration using the same method as main application
	cfg := config.LoadConfig()

	// Apply test-specific overrides
	cfg.ServerPort = "0" // Use random port for tests
	cfg.BaseURL = "http://localhost"
	cfg.JWTSecret = "test-secret-key"
	cfg.UploadPath = getUploadPath()
	cfg.TestMode = true
	cfg.RateLimitEnabled = false
	cfg.RequestsPerMinute = 1000

	// Connect to test database using the same method as main application
	// Use max_conns=1 to prevent connection pool exhaustion during test runs
	// (78 tests × 1 connection = 78 connections, well under PostgreSQL's 100 default max_connections)
	dbConfig, err := pgxpool.ParseConfig(cfg.DatabaseURL())
	require.NoError(t, err, "Failed to parse database URL")
	dbConfig.MaxConns = 1
	dbPool, err := pgxpool.NewWithConfig(context.Background(), dbConfig)
	require.NoError(t, err, "Failed to connect to test database")

	queries := database.New(dbPool)

	// Create login attempt tracker
	loginAttemptTracker := ratelimit.NewLoginAttemptTracker(5, 15*time.Minute, 5*time.Minute)

	// Create handlers
	authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker)
	libraryHandler := handlers.NewLibraryHandler(queries)
	deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
	deviceAuthMiddleware := middleware.NewDeviceAuthMiddleware(queries)

	// Create WebSocket connection manager
	connManager := wsync.NewConnectionManager()
	cleanupCancel := connManager.StartCleanupTask()

	progressService := wsync.NewProgressService(queries, connManager)
	annotationService := wsync.NewAnnotationService(queries, connManager)

	queueProcessor := wsync.NewSyncQueueProcessor(queries)
	queueProcessor.SetProgressService(progressService)
	queueCtx, queueCancel := context.WithCancel(context.Background())
	go queueProcessor.Start(queueCtx)

	koreaderHandler := handlers.NewKOReaderHandler(queries, connManager, queueProcessor)
	koreaderHandler.SetProgressService(progressService)
	koreaderHandler.SetAnnotationService(annotationService)
	wsHandler := handlers.NewWSHandler(queries, connManager, cfg.JWTSecret, deviceAuthMiddleware)
	conflictHandler := handlers.NewConflictHandler(queries, connManager)
	analyticsHandler := handlers.NewAnalyticsHandler(queries)
	queueHandler := handlers.NewQueueHandler(queries, queueProcessor)
	systemSettingsHandler := handlers.NewSystemSettingsHandler(queries)
	processingIssuesHandler := handlers.NewProcessingIssuesHandler(queries)

	// Create refactored handlers (matching main.go)
	libraryService := services.NewLibraryService(queries)
	worker := services.NewWorker(3, connManager)
	services.WorkerInstance = worker
	jobsHandler := handlers.NewJobsHandler(queries, worker)
	collectionHandler := handlers.NewCollectionHandler(queries, libraryService, connManager)
	filtersHandler := handlers.NewFiltersHandler(queries)
	dashboardService := services.NewDashboardService(queries)
	dashboardHandler := handlers.NewDashboardHandler(queries)
	seriesHandler := handlers.NewSeriesHandler(queries)
	mediaHandler := handlers.NewMediaHandler(queries, libraryService, worker)
	mediaHandler.SetProgressService(progressService)
	mediaHandler.SetAnnotationService(annotationService)
	matchingHandler := handlers.NewMatchingHandler(queries, connManager)

	// Create conversion service for OPDS
	conversionService := services.NewConversionService(queries, getCachePath())
	opdsHandler := handlers.NewOPDSHandler(queries, libraryService, conversionService)

	// Create Echo instance
	e := echo.New()

	// Set up validator
	v := validator.New()
	if err := ratelimit.RegisterPasswordValidation(v); err != nil {
		t.Fatal("Failed to register password validator:", err)
	}
	e.Validator = &CustomValidator{validator: v}

	// Middleware
	e.Use(echomiddleware.RequestLogger())
	e.Use(echomiddleware.Recover())
	e.Use(echomiddleware.CORSWithConfig(echomiddleware.CORSConfig{
		AllowOrigins:     []string{"*"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: false,
	}))

	// Setup routes using router package
	routerConfig := &router.Config{
		Echo:                  e,
		Queries:               queries,
		Cfg:                   cfg,
		DBPool:                dbPool,
		AuthHandler:           authHandler,
		LibraryHandler:        libraryHandler,
		DeviceHandler:         deviceHandler,
		MediaHandler:          mediaHandler,
		MatchingHandler:       matchingHandler,
		KOReaderHandler:       koreaderHandler,
		WSHandler:             wsHandler,
		ConflictHandler:       conflictHandler,
		AnalyticsHandler:      analyticsHandler,
		QueueHandler:          queueHandler,
		SystemSettingsHandler: systemSettingsHandler,
		ProcessingIssuesHandler: processingIssuesHandler,
		CollectionHandler:     collectionHandler,
		FiltersHandler:        filtersHandler,
		DashboardHandler:      dashboardHandler,
		DashboardService:      dashboardService,
		SeriesHandler:         seriesHandler,
		OPDSHandler:           opdsHandler,
		JobsHandler:           jobsHandler,
		ConnManager:           connManager,
		QueueProcessor:        queueProcessor,
		ProgressService:       progressService,
		AnnotationService:     annotationService,
		DeviceAuthMiddleware:  deviceAuthMiddleware,
		LoginTracker:          loginAttemptTracker,
	}

	router.RegisterRoutes(routerConfig)

	ln, err := net.Listen("tcp", "127.0.0.1:0")
	require.NoError(t, err, "Failed to create listener")
	// Configure Echo's HTTP server with the listener
	serverConfig := &http.Server{
		Handler: e,
		Addr:    ln.Addr().String(),
	}
	ts := &httptest.Server{
		Listener: ln,
		Config:   serverConfig,
	}
	ts.Start()

	ctx := context.Background()

	// Delete transient test users but preserve the dev admin user
	// testuser@tests.bookhoard.internal is the shared dev admin — deleting it
	// triggers ON DELETE SET NULL on libraries.created_by_admin_id
	allUsers, err := queries.ListUsers(ctx)
	if err == nil {
		for _, user := range allUsers {
			if user.Email == "testuser@tests.bookhoard.internal" {
				continue
			}
			if strings.HasSuffix(user.Email, "@example.com") || strings.HasSuffix(user.Email, "@tests.bookhoard.internal") {
				queries.DeleteUser(ctx, user.ID)
			}
		}
	}

	// Delete test libraries (names containing "test" - case insensitive)
	// This cleans up libraries created by tests while preserving user-created libraries
	// NOTE: Do not use "test" in library names if you want to keep them!
	allLibs, _ := queries.ListLibraries(ctx)
	for _, lib := range allLibs {
		if strings.Contains(strings.ToLower(lib.Name), "test") {
			queries.DeleteLibrary(ctx, lib.ID)
		}
	}

	// Create fresh admin test user
	passwordHash := "$2a$10$JjAtK7PPa1WexQC3AUGe8OXLeuseZ/haN1Mz7emMo6CfOvMiTVXWq"
	adminUser, err := queries.CreateUser(ctx, database.CreateUserParams{
		Email:        "testuser@tests.bookhoard.internal",
		Username:     "testuser",
		PasswordHash: passwordHash,
		FirstName:    pgtype.Text{String: "Test", Valid: true},
		LastName:     pgtype.Text{String: "User", Valid: true},
		Role:         "admin",
	})
	require.NoError(t, err, "Failed to create admin test user")

	adminUUID, err := uuid.FromBytes(adminUser.ID.Bytes[:])
	require.NoError(t, err, "Failed to parse admin user UUID")
	createDefaultCollectionsForUser(t, queries, pgtype.UUID{Bytes: adminUUID, Valid: true})

	// Create fresh regular test user
	regularUser, err := queries.CreateUser(ctx, database.CreateUserParams{
		Email:        "testregularuser@tests.bookhoard.internal",
		Username:     "testregularuser",
		PasswordHash: passwordHash,
		FirstName:    pgtype.Text{String: "Regular", Valid: true},
		LastName:     pgtype.Text{String: "User", Valid: true},
		Role:         "user",
	})
	require.NoError(t, err, "Failed to create regular test user")

	regularUUID, err := uuid.FromBytes(regularUser.ID.Bytes[:])
	require.NoError(t, err, "Failed to parse regular user UUID")
	createDefaultCollectionsForUser(t, queries, pgtype.UUID{Bytes: regularUUID, Valid: true})

	// Login to get tokens
	adminToken := loginWithCredentials(t, ts, "testuser@tests.bookhoard.internal", "Test@Pass123!")
	regularToken := loginWithCredentials(t, ts, "testregularuser@tests.bookhoard.internal", "Test@Pass123!")

	// Create TestServerSetup struct with all resources
	setup := &TestServerSetup{
		Server:          ts,
		DB:              queries,
		DBPool:          dbPool,
		Config:          cfg,
		ConnManager:     connManager,
		QueueProcessor:  queueProcessor,
		ProgressService: progressService,
		CleanupCancel:   cleanupCancel,
		QueueCtx:        queueCtx,
		QueueCancel:     queueCancel,
		Token:           adminToken,
		RegularToken:    regularToken,
	}

	// Register cleanup function to run automatically when test completes
	t.Cleanup(func() {
		// Clean up test libraries created by this test
		ctx := context.Background()
		allLibs, err := queries.ListLibraries(ctx)
		if err == nil {
			for _, lib := range allLibs {
				if strings.Contains(strings.ToLower(lib.Name), "test") {
					queries.DeleteLibrary(ctx, lib.ID)
				}
			}
		}

		if err := setup.Close(); err != nil {
			t.Errorf("Failed to cleanup test server: %v", err)
		}
	})

	return setup
}

func loginWithCredentials(t *testing.T, ts *httptest.Server, email, password string) string {
	loginRequest := map[string]interface{}{
		"login":    email,
		"password": password,
	}
	body, _ := json.Marshal(loginRequest)

	req, _ := http.NewRequest("POST", ts.URL+"/api/auth/login", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err, "Failed to login")
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusOK, resp.StatusCode, "Login should succeed")

	var result map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(t, err)

	token, ok := result["access_token"].(string)
	require.True(t, ok, "Should have access_token")
	require.NotEmpty(t, token, "Access token should not be empty")

	return token
}

func getTestUserID(t *testing.T, db *database.Queries) uuid.UUID {
	ctx := context.Background()
	user, err := db.GetUserByEmail(ctx, "testuser@tests.bookhoard.internal")
	require.NoError(t, err, "Test user should exist")
	userUUID, err := uuid.FromBytes(user.ID.Bytes[:])
	require.NoError(t, err, "Failed to parse user UUID")
	return userUUID
}

func getRegularUserID(t *testing.T, db *database.Queries) uuid.UUID {
	ctx := context.Background()
	user, err := db.GetUserByEmail(ctx, "testregularuser@tests.bookhoard.internal")
	require.NoError(t, err, "Regular user should exist")
	userUUID, err := uuid.FromBytes(user.ID.Bytes[:])
	require.NoError(t, err, "Failed to parse user UUID")
	return userUUID
}

// createTestMediaItemID creates a test media item and returns its ID
func createTestMediaItemID(t *testing.T, setup *TestServerSetup) string {
	uniqueName := fmt.Sprintf("Test Library %d", time.Now().UnixNano())
	httpClient := &http.Client{}

	libReq := map[string]interface{}{
		"name":        uniqueName,
		"description": "A test library for media items",
		"type":        "ebooks",
	}
	libBody, _ := json.Marshal(libReq)

	req, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries", bytes.NewBuffer(libBody))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+setup.Token)

	resp, err := httpClient.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)

	require.Equal(t, http.StatusCreated, resp.StatusCode)

	var libResult map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&libResult)
	require.NoError(t, err)

	libData := libResult["id"].(string)

	folderReq := map[string]interface{}{
		"folder_path": "/app/uploads",
	}
	folderBody, _ := json.Marshal(folderReq)

	folderReqHTTP, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries/"+libData+"/folders", bytes.NewBuffer(folderBody))
	folderReqHTTP.Header.Set("Content-Type", "application/json")
	folderReqHTTP.Header.Set("Authorization", "Bearer "+setup.Token)

	folderResp, err := httpClient.Do(folderReqHTTP)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(folderResp.Body)
	require.Equal(t, http.StatusCreated, folderResp.StatusCode, "Library folder creation is required before adding media items")

	mediaItemReq := map[string]interface{}{
		"library_id": libData,
		"title":      "Test Media Item",
		"author":     "Test Author",
		"file_path":  "/tmp/test.epub",
		"file_size":  1024,
		"mime_type":  "application/epub+zip",
	}
	mediaItemBody, _ := json.Marshal(mediaItemReq)

	req2, _ := http.NewRequest("POST", setup.Server.URL+"/api/media-items", bytes.NewBuffer(mediaItemBody))
	req2.Header.Set("Content-Type", "application/json")
	req2.Header.Set("Authorization", "Bearer "+setup.Token)

	resp2, err := httpClient.Do(req2)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp2.Body)

	require.Equal(t, http.StatusCreated, resp2.StatusCode)

	var mediaItemResult map[string]interface{}
	err = json.NewDecoder(resp2.Body).Decode(&mediaItemResult)
	require.NoError(t, err)

	mediaItemID := mediaItemResult["id"].(string)

	t.Cleanup(func() {
		deleteReq, _ := http.NewRequest("DELETE", setup.Server.URL+"/api/libraries/"+libData, nil)
		deleteReq.Header.Set("Authorization", "Bearer "+setup.Token)
		httpClient.Do(deleteReq)
	})

	return mediaItemID
}

// addFolderToLibrary adds a folder to a test library via HTTP API
func addFolderToLibrary(t *testing.T, setup *TestServerSetup, libraryID string, folderPath string) {
	t.Helper()

	payload := map[string]interface{}{
		"folder_path": folderPath,
	}

	body, _ := json.Marshal(payload)
	req, _ := http.NewRequest("POST", setup.Server.URL+"/api/libraries/"+libraryID+"/folders", bytes.NewBuffer(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+setup.Token)

	client := &http.Client{}
	resp, err := client.Do(req)
	require.NoError(t, err)
	defer func(Body io.ReadCloser) {
		_ = Body.Close()
	}(resp.Body)
	require.Equal(t, http.StatusCreated, resp.StatusCode)
}
