package main

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

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

// createTestQueueItem creates a complete test fixture: device, library, media item, and sync queue item
func createTestQueueItem(t *testing.T, db *database.Queries, userID uuid.UUID) (database.SyncQueue, database.Devices) {
	ctx := context.Background()

	// 1. Create device
	deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
	device, err := db.CreateDevice(ctx, database.CreateDeviceParams{
		UserID:           pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
		DeviceName:       "Test Device",
		DeviceType:       "koreader",
		DeviceIdentifier: "test-device-" + uuid.New().String(),
		AuthToken:        deviceToken,
		SyncEnabled:      pgtype.Bool{Bool: true, Valid: true},
		AutoSync:         pgtype.Bool{Bool: true, Valid: true},
		DeviceMetadata:   []byte("{}"),
	})
	require.NoError(t, err, "Should create device")

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

	// 3. Create library
	library, err := db.CreateLibrary(ctx, database.CreateLibraryParams{
		Name:             "Test Queue Library",
		Description:      pgtype.Text{String: "Test library", Valid: true},
		LibraryTypeID:    libraryType.ID,
		CreatedByAdminID: pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
	})
	require.NoError(t, err, "Should create library")

	// 4. Create media item
	mediaItem, err := db.CreateMediaItem(ctx, database.CreateMediaItemParams{
		LibraryID: library.ID,
		Title:     "Test Book for Queue",
		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, "Should create media item")

	// 5. Create sync queue item
	queueItem, err := db.CreateSyncQueueItem(ctx, database.CreateSyncQueueItemParams{
		DeviceID:    device.ID,
		MediaItemID: mediaItem.ID,
		SyncType:    "progress",
		SyncData:    []byte(`{"percentage": 50}`),
		Priority:    pgtype.Int4{Int32: 5, Valid: true},
	})
	require.NoError(t, err, "Should create sync queue item")

	return queueItem, device
}

func TestListAllQueueItems_Admin(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	req := httptest.NewRequest("GET", "/api/queue/items", nil)
	req.Header.Set("Authorization", "Bearer "+token)
	rec := httptest.NewRecorder()
	setup.Server.Config.Handler.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code, "Should list all queue items")

	var response map[string]interface{}
	json.Unmarshal(rec.Body.Bytes(), &response)
	assert.NotNil(t, response["items"], "Should have queue items")
}

func TestGetDeviceQueueStats(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	userID := getTestUserID(t, setup.DB)

	deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
	device, err := setup.DB.CreateDevice(context.Background(), database.CreateDeviceParams{
		UserID:           pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
		DeviceName:       "Test Device",
		DeviceType:       "koreader",
		DeviceIdentifier: "test-device-123",
		AuthToken:        deviceToken,
		SyncEnabled:      pgtype.Bool{Bool: true, Valid: true},
		AutoSync:         pgtype.Bool{Bool: true, Valid: true},
		DeviceMetadata:   []byte("{}"),
	})
	require.NoError(t, err, "Should create device")

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

	req := httptest.NewRequest("GET", fmt.Sprintf("/api/queue/devices/%s/stats", deviceID.String()), nil)
	req.Header.Set("Authorization", "Bearer "+token)
	rec := httptest.NewRecorder()
	setup.Server.Config.Handler.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code, "Should get device queue stats")

	var response map[string]interface{}
	json.Unmarshal(rec.Body.Bytes(), &response)
	assert.Contains(t, response, "total_count", "Should have total_count")
	assert.Contains(t, response, "pending_count", "Should have pending_count")
	assert.Contains(t, response, "processing_count", "Should have processing_count")
	assert.Contains(t, response, "completed_count", "Should have completed_count")
	assert.Contains(t, response, "failed_count", "Should have failed_count")
}

func TestListDeviceQueueItems(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	userID := getTestUserID(t, setup.DB)

	deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
	device, err := setup.DB.CreateDevice(context.Background(), database.CreateDeviceParams{
		UserID:           pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
		DeviceName:       "Test Device",
		DeviceType:       "koreader",
		DeviceIdentifier: "test-device-123",
		AuthToken:        deviceToken,
		SyncEnabled:      pgtype.Bool{Bool: true, Valid: true},
		AutoSync:         pgtype.Bool{Bool: true, Valid: true},
		DeviceMetadata:   []byte("{}"),
	})
	require.NoError(t, err, "Should create device")

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

	req := httptest.NewRequest("GET", fmt.Sprintf("/api/queue/devices/%s/items", deviceID.String()), nil)
	req.Header.Set("Authorization", "Bearer "+token)
	rec := httptest.NewRecorder()
	setup.Server.Config.Handler.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code, "Should list device queue items")

	var response map[string]interface{}
	json.Unmarshal(rec.Body.Bytes(), &response)
	items, ok := response["items"].([]interface{})
	assert.True(t, ok, "Should have items array")
	assert.NotNil(t, items, "Should have queue items")
}

func TestRetryQueueItem(t *testing.T) {
	setup := setupTestServer(t)
	token := setup.Token

	// Get the current test user's ID from database
	user, err := setup.DB.GetUserByEmail(context.Background(), "testuser@tests.bookhoard.internal")
	require.NoError(t, err, "Should get test user")
	userID, err := uuid.FromBytes(user.ID.Bytes[:])
	require.NoError(t, err, "Should parse user UUID")

	queueItem, _ := createTestQueueItem(t, setup.DB, userID)
	itemID := uuid.UUID(queueItem.ID.Bytes).String()

	req := httptest.NewRequest("POST", fmt.Sprintf("/api/queue/items/%s/retry", itemID), nil)
	req.Header.Set("Authorization", "Bearer "+token)
	rec := httptest.NewRecorder()
	setup.Server.Config.Handler.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code, "Should retry queue item")
}

func TestDeleteQueueItem(t *testing.T) {
	setup := setupTestServer(t)
	token := setup.Token

	// Get the current test user's ID from database
	user, err := setup.DB.GetUserByEmail(context.Background(), "testuser@tests.bookhoard.internal")
	require.NoError(t, err, "Should get test user")
	userID, err := uuid.FromBytes(user.ID.Bytes[:])
	require.NoError(t, err, "Should parse user UUID")

	queueItem, _ := createTestQueueItem(t, setup.DB, userID)
	itemID := uuid.UUID(queueItem.ID.Bytes).String()

	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/queue/items/%s", itemID), nil)
	req.Header.Set("Authorization", "Bearer "+token)
	rec := httptest.NewRecorder()
	setup.Server.Config.Handler.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusNoContent, rec.Code, "Should delete queue item")
}

func TestClearDeviceQueue(t *testing.T) {
	setup := setupTestServer(t)

	token := setup.Token

	userID := getTestUserID(t, setup.DB)

	deviceToken := fmt.Sprintf("dev_%s", uuid.New().String())
	device, err := setup.DB.CreateDevice(context.Background(), database.CreateDeviceParams{
		UserID:           pgtype.UUID{Bytes: [16]byte(userID), Valid: true},
		DeviceName:       "Test Device",
		DeviceType:       "koreader",
		DeviceIdentifier: "test-device-123",
		AuthToken:        deviceToken,
		SyncEnabled:      pgtype.Bool{Bool: true, Valid: true},
		AutoSync:         pgtype.Bool{Bool: true, Valid: true},
		DeviceMetadata:   []byte("{}"),
	})
	require.NoError(t, err, "Should create device")

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

	req := httptest.NewRequest("DELETE", fmt.Sprintf("/api/queue/devices/%s/clear", deviceID.String()), nil)
	req.Header.Set("Authorization", "Bearer "+token)
	rec := httptest.NewRecorder()
	setup.Server.Config.Handler.ServeHTTP(rec, req)

	assert.Equal(t, http.StatusOK, rec.Code, "Should clear device queue")
}

func TestQueueEndpoints_Unauthorized(t *testing.T) {
	setup := setupTestServer(t)

	tests := []struct {
		name           string
		method         string
		endpoint       string
		body           []byte
		expectedStatus int
	}{
		{"ListAllQueueItems", "GET", "/api/queue/items", nil, http.StatusUnauthorized},
		{"GetDeviceQueueStats", "GET", "/api/queue/devices/test-device-id/stats", nil, http.StatusUnauthorized},
		{"ListDeviceQueueItems", "GET", "/api/queue/devices/test-device-id/items", nil, http.StatusUnauthorized},
		{"RetryQueueItem", "POST", "/api/queue/items/test-item-id/retry", nil, http.StatusUnauthorized},
		{"DeleteQueueItem", "DELETE", "/api/queue/items/test-item-id", nil, http.StatusUnauthorized},
		{"ClearDeviceQueue", "DELETE", "/api/queue/devices/test-device-id/clear", nil, http.StatusUnauthorized},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(tt.method, tt.endpoint, bytes.NewReader(tt.body))
			rec := httptest.NewRecorder()
			setup.Server.Config.Handler.ServeHTTP(rec, req)

			assert.Equal(t, tt.expectedStatus, rec.Code, "Should require authentication")
		})
	}
}
