package main

import (
	"bookhoard/internal/config"
	"bookhoard/internal/database"
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

// setupSyncTestDB creates a database connection for sync integration tests
func setupSyncTestDB(t *testing.T) *database.Queries {
	ctx := context.Background()

	cfg := config.LoadConfig()
	cfg.DatabaseHost = "db"
	dbURL := cfg.DatabaseURL()
	dbConfig, err := pgxpool.ParseConfig(dbURL)
	require.NoError(t, err, "Failed to parse database URL")
	dbConfig.MaxConns = 1

	dbPool, err := pgxpool.NewWithConfig(ctx, dbConfig)
	require.NoError(t, err, "Failed to connect to test database")

	db := database.New(dbPool)
	t.Cleanup(func() {
		_, _ = dbPool.Exec(ctx, "DELETE FROM sync_queue WHERE true")
		_, _ = dbPool.Exec(ctx, "DELETE FROM reading_history WHERE true")
		_, _ = dbPool.Exec(ctx, "DELETE FROM media_items WHERE title LIKE 'Test %'")
		_, _ = dbPool.Exec(ctx, "DELETE FROM libraries WHERE name LIKE 'Test %'")
		_, _ = dbPool.Exec(ctx, "DELETE FROM devices WHERE device_name LIKE 'Test %'")
		_, _ = dbPool.Exec(ctx, "DELETE FROM users WHERE email LIKE 'test-sync%'")
		dbPool.Close()
	})

	return db
}

func createSyncTestUser(t *testing.T, db *database.Queries) pgtype.UUID {
	ctx := context.Background()
	hashedPassword := "$2a$10$rKvZHX3lIJ6CpH1lOukQ/xU8j5cH8mYHYP5YGfXllq5hG8y0Ou"

	user, err := db.CreateUser(ctx, database.CreateUserParams{
		Email:        "test-sync@example.com",
		Username:     "testsyncuser",
		PasswordHash: hashedPassword,
		FirstName:    pgtype.Text{String: "Test", Valid: true},
		LastName:     pgtype.Text{String: "Sync", Valid: true},
		Role:         "user",
	})
	require.NoError(t, err)

	return user.ID
}

func createSyncTestDevice(t *testing.T, db *database.Queries, userID pgtype.UUID) pgtype.UUID {
	ctx := context.Background()
	deviceID := uuid.New()

	device, err := db.CreateDevice(ctx, database.CreateDeviceParams{
		UserID:               userID,
		DeviceName:           "Test Sync Device",
		DeviceType:           "koreader",
		DeviceIdentifier:     deviceID.String(),
		AuthToken:            "test-sync-token-" + deviceID.String(),
		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)

	return device.ID
}

func createSyncTestMedia(t *testing.T, db *database.Queries, libraryID pgtype.UUID) pgtype.UUID {
	ctx := context.Background()

	media, err := db.CreateMediaItem(ctx, database.CreateMediaItemParams{
		LibraryID: libraryID,
		Title:     "Sync Test Book",
		Author:    pgtype.Text{String: "Test Author", Valid: true},
		FilePath:  "/tmp/test.epub",
		FileSize:  pgtype.Int8{Int64: 1024, Valid: true},
		MimeType:  pgtype.Text{String: "application/epub+zip", Valid: true},
	})
	require.NoError(t, err)

	return media.ID
}

func createSyncTestLibrary(t *testing.T, db *database.Queries, userID pgtype.UUID) pgtype.UUID {
	ctx := context.Background()

	libraryType, err := db.GetLibraryTypeByName(ctx, "ebooks")
	require.NoError(t, err, "Should find library type")

	library, err := db.CreateLibrary(ctx, database.CreateLibraryParams{
		Name:             "Test Sync Library",
		Description:      pgtype.Text{String: "Test library for sync", Valid: true},
		LibraryTypeID:    libraryType.ID,
		CreatedByAdminID: userID,
	})
	require.NoError(t, err)

	return library.ID
}

// TestSyncFull_Initial tests full sync with initial device state
func TestSyncFull_Initial(t *testing.T) {
	ctx := context.Background()
	db := setupSyncTestDB(t)
	userID := createSyncTestUser(t, db)
	libraryID := createSyncTestLibrary(t, db, userID)
	deviceID := createSyncTestDevice(t, db, userID)
	mediaID := createSyncTestMedia(t, db, libraryID)

	// Create reading history entry
	now := time.Now()
	_, err := db.CreateReadingHistory(ctx, database.CreateReadingHistoryParams{
		UserID:              userID,
		MediaItemID:         mediaID,
		DeviceID:            deviceID,
		ProgressPercentage:  pgtype.Float8{Float64: 25.0, Valid: true},
		ReadingSessionStart: pgtype.Timestamptz{Time: now, Valid: true},
		PagesRead:           pgtype.Int4{Int32: 50, Valid: true},
		TimeSpentSeconds:    pgtype.Int4{Int32: 300, Valid: true},
	})
	require.NoError(t, err)
}

// TestSyncIncremental tests incremental sync
func TestSyncIncremental(t *testing.T) {
	ctx := context.Background()
	db := setupSyncTestDB(t)
	userID := createSyncTestUser(t, db)
	libraryID := createSyncTestLibrary(t, db, userID)
	deviceID := createSyncTestDevice(t, db, userID)
	mediaID := createSyncTestMedia(t, db, libraryID)

	// Create initial reading history entry
	now := time.Now()
	_, err := db.CreateReadingHistory(ctx, database.CreateReadingHistoryParams{
		UserID:              userID,
		MediaItemID:         mediaID,
		DeviceID:            deviceID,
		ProgressPercentage:  pgtype.Float8{Float64: 50.0, Valid: true},
		ReadingSessionStart: pgtype.Timestamptz{Time: now, Valid: true},
		PagesRead:           pgtype.Int4{Int32: 100, Valid: true},
		TimeSpentSeconds:    pgtype.Int4{Int32: 600, Valid: true},
	})
	require.NoError(t, err)
}

// TestSyncQueueProcessor tests the sync queue processor
func TestSyncQueueProcessor(t *testing.T) {
	ctx := context.Background()
	db := setupSyncTestDB(t)
	userID := createSyncTestUser(t, db)
	libraryID := createSyncTestLibrary(t, db, userID)
	deviceID := createSyncTestDevice(t, db, userID)
	mediaID := createSyncTestMedia(t, db, libraryID)

	// Create sync queue item
	_, err := db.CreateSyncQueueItem(ctx, database.CreateSyncQueueItemParams{
		DeviceID:    deviceID,
		MediaItemID: mediaID,
		SyncType:    "progress",
		SyncData:    []byte(`{"percentage": 50}`),
		Priority:    pgtype.Int4{Int32: 5, Valid: true},
		MaxAttempts: pgtype.Int4{Int32: 3, Valid: true},
		Status:      pgtype.Text{String: "pending", Valid: true},
	})
	require.NoError(t, err)

	// Verify queue item was created
	items, err := db.ListPendingSyncQueueItems(ctx, database.ListPendingSyncQueueItemsParams{
		DeviceID: deviceID,
		Limit:    int32(10),
	})
	require.NoError(t, err)
	assert.Equal(t, 1, len(items), "Should have one queue item")
	assert.Equal(t, "pending", items[0].Status.String)
}

// TestSyncConflictDetection tests conflict detection
func TestSyncConflictDetection(t *testing.T) {
	ctx := context.Background()
	db := setupSyncTestDB(t)
	userID := createSyncTestUser(t, db)
	libraryID := createSyncTestLibrary(t, db, userID)
	deviceID := createSyncTestDevice(t, db, userID)
	mediaID := createSyncTestMedia(t, db, libraryID)

	// Create reading history entry
	_, err := db.CreateReadingHistory(ctx, database.CreateReadingHistoryParams{
		UserID:              userID,
		MediaItemID:         mediaID,
		DeviceID:            deviceID,
		ProgressPercentage:  pgtype.Float8{Float64: 50.0, Valid: true},
		ReadingSessionStart: pgtype.Timestamptz{Time: time.Now().Add(-1 * time.Hour), Valid: true},
		PagesRead:           pgtype.Int4{Int32: 100, Valid: true},
		TimeSpentSeconds:    pgtype.Int4{Int32: 600, Valid: true},
	})
	require.NoError(t, err)

	// Create sync queue item with different percentage (simulating conflict)
	_, err = db.CreateSyncQueueItem(ctx, database.CreateSyncQueueItemParams{
		DeviceID:    deviceID,
		MediaItemID: mediaID,
		SyncType:    "progress",
		SyncData:    []byte(`{"percentage": 75}`),
		Priority:    pgtype.Int4{Int32: 5, Valid: true},
		MaxAttempts: pgtype.Int4{Int32: 3, Valid: true},
		Status:      pgtype.Text{String: "pending", Valid: true},
	})
	require.NoError(t, err)

	// Verify both exist (conflict detection would happen during processing)
	items, err := db.ListPendingSyncQueueItems(ctx, database.ListPendingSyncQueueItemsParams{
		DeviceID: deviceID,
		Limit:    int32(10),
	})
	require.NoError(t, err)
	assert.Equal(t, 1, len(items), "Should have one queue item")
}

// TestSyncWithDeviceAuth tests device authentication in sync
func TestSyncWithDeviceAuth(t *testing.T) {
	setup := setupDeviceTest(t)
	defer setup.Server.Close()

	// Create a device
	device := setup.CreateDevice(t, "Test Sync Device", "koreader", "sync-test-123")

	// Verify device exists in database
	ctx := context.Background()
	pgDeviceID := pgtype.UUID{Bytes: [16]byte(device.ID), Valid: true}
	dbDevice, err := setup.DB.GetDevice(ctx, pgDeviceID)
	require.NoError(t, err)
	assert.Equal(t, "Test Sync Device", dbDevice.DeviceName)
}

// TestSyncEndpoint tests sync endpoint with authentication
func TestSyncEndpoint(t *testing.T) {
	setup := setupDeviceTest(t)
	defer setup.Server.Close()

	device := setup.CreateDevice(t, "Test Sync Device", "koreader", "sync-test-123")

	// Test sync endpoint with device token
	req := httptest.NewRequest("POST", "/api/sync/koreader/progress", nil)
	req.Header.Set("Authorization", "Bearer "+device.AuthToken)
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()

	setup.Server.Config.Handler.ServeHTTP(rec, req)

	// Should get a response (may be success or error depending on payload)
	assert.NotEqual(t, http.StatusUnauthorized, rec.Code, "Should not be unauthorized with valid device token")
}
