package main

import (
	"bookhoard/internal/database"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Helper functions for database verification and test utilities
// These functions reduce code duplication and ensure consistent database state verification

// verifyDeviceCreated verifies a device exists in database with expected values
func verifyDeviceCreated(t *testing.T, db *database.Queries, deviceID uuid.UUID, expectedName, expectedType, expectedIdentifier string) {
	pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
	device, err := db.GetDevice(context.Background(), pgDeviceID)
	require.NoError(t, err, "Device should exist in database")

	assert.Equal(t, expectedName, device.DeviceName, "Device name should match")
	assert.Equal(t, expectedType, device.DeviceType, "Device type should match")
	assert.Equal(t, expectedIdentifier, device.DeviceIdentifier, "Device identifier should match")
	assert.NotEmpty(t, device.AuthToken, "Device should have auth token")
}

// verifyDeviceDeleted verifies a device does not exist in database
func verifyDeviceDeleted(t *testing.T, db *database.Queries, deviceID uuid.UUID) {
	pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
	_, err := db.GetDevice(context.Background(), pgDeviceID)
	assert.Error(t, err, "Device should be deleted from database")
}

// verifyUserField verifies a user has expected field value in database
func verifyUserField(t *testing.T, db *database.Queries, userID uuid.UUID, field string, expected interface{}) {
	pgUserID := pgtype.UUID{Bytes: [16]byte(userID), Valid: true}
	user, err := db.GetUser(context.Background(), pgUserID)
	require.NoError(t, err, "User should exist in database")

	switch field {
	case "email":
		if em, ok := expected.(string); ok {
			assert.Equal(t, em, user.Email, "Email should match")
		}
	case "first_name":
		if fn, ok := expected.(string); ok {
			assert.Equal(t, fn, user.FirstName.String, "First name should match")
		}
	case "last_name":
		if ln, ok := expected.(string); ok {
			assert.Equal(t, ln, user.LastName.String, "Last name should match")
		}
	case "username":
		if un, ok := expected.(string); ok {
			assert.Equal(t, un, user.Username, "Username should match")
		}
	case "theme":
		if th, ok := expected.(string); ok {
			assert.Equal(t, th, user.Theme.String, "Theme should match")
		}
	}
}

// verifyMediaItemInDB verifies a media item exists in database
func verifyMediaItemInDB(t *testing.T, db *database.Queries, mediaID uuid.UUID) {
	pgMediaID := pgtype.UUID{Bytes: [16]byte(mediaID), Valid: true}
	_, err := db.GetMediaItem(context.Background(), pgMediaID)
	require.NoError(t, err, "Media item should exist in database")
}

// verifyMediaItemDeleted verifies a media item does not exist in database
func verifyMediaItemDeleted(t *testing.T, db *database.Queries, mediaID uuid.UUID) {
	pgMediaID := pgtype.UUID{Bytes: [16]byte(mediaID), Valid: true}
	_, err := db.GetMediaItem(context.Background(), pgMediaID)
	assert.Error(t, err, "Media item should be deleted from database")
}

// createTestLibraryWithFolder creates a test library with optional folder
func createTestLibraryWithFolder(t *testing.T, ts *httptest.Server, token, name string, withFolder bool) string {
	libReq := map[string]interface{}{
		"name": name,
		"type": "ebooks",
	}
	libBody, _ := json.Marshal(libReq)

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

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

	var libResponse map[string]interface{}
	err = json.NewDecoder(resp.Body).Decode(&libResponse)
	require.NoError(t, err)
	libraryID := libResponse["id"].(string)

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

		folderHTTP, _ := http.NewRequest("POST", fmt.Sprintf("%s/api/libraries/%s/folders", ts.URL, libraryID), bytes.NewBuffer(folderBody))
		folderHTTP.Header.Set("Content-Type", "application/json")
		folderHTTP.Header.Set("Authorization", "Bearer "+token)

		folderResp, err := client.Do(folderHTTP)
		require.NoError(t, err)
		defer func(Body io.ReadCloser) {
			_ = Body.Close()
		}(folderResp.Body)
		require.Equal(t, http.StatusCreated, folderResp.StatusCode, "Folder creation should succeed")
	}

	return libraryID
}

// runConcurrent executes functions concurrently and waits for all to complete
func runConcurrent(t *testing.T, maxConcurrent int, fns []func() error) []error {
	if len(fns) == 0 {
		return nil
	}
	if len(fns) < maxConcurrent {
		maxConcurrent = len(fns)
	}

	errors := make(chan error, len(fns))
	var wg sync.WaitGroup

	for i := 0; i < maxConcurrent; i++ {
		wg.Add(1)
		go func(idx int) {
			defer wg.Done()
			if err := fns[idx](); err != nil {
				errors <- err
			}
		}(i)
	}

	wg.Wait()
	close(errors)

	var allErrors []error
	for err := range errors {
		allErrors = append(allErrors, err)
	}

	return allErrors
}
