package main

import (
	"bookhoard/internal/database"
	"bookhoard/internal/services"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"testing"
	"time"

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

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

	token := setup.Token

	// Create library
	createLibReq := map[string]interface{}{
		"name":        "Worker Test Library",
		"description": "Test library for worker",
		"type":        "ebooks",
	}
	createLibURL := setup.Server.URL + "/api/libraries"
	createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(jsonMarshal(createLibReq)))
	createLibReqHTTP.Header.Set("Content-Type", "application/json")
	createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	createLibResp, err := client.Do(createLibReqHTTP)
	require.NoError(t, err)
	require.Equal(t, http.StatusCreated, createLibResp.StatusCode)

	var createLibResponse map[string]interface{}
	err = json.NewDecoder(createLibResp.Body).Decode(&createLibResponse)
	require.NoError(t, err)
	_ = createLibResp.Body.Close()

	libraryID, ok := createLibResponse["id"].(string)
	require.True(t, ok)
	require.NotEmpty(t, libraryID)

	// Create a temporary directory for testing
	tmpDir := t.TempDir()

	// Add folder to library
	folderURL := fmt.Sprintf("%s/api/libraries/%s/folders", setup.Server.URL, libraryID)
	folderReq := map[string]interface{}{
		"folder_path": tmpDir,
	}
	req, _ := http.NewRequest("POST", folderURL, bytes.NewBuffer(jsonMarshal(folderReq)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := client.Do(req)
	require.NoError(t, err)
	_ = resp.Body.Close()
	require.Equal(t, http.StatusCreated, resp.StatusCode)

	// Create test files in the directory
	for i := 0; i < 3; i++ {
		filePath := filepath.Join(tmpDir, fmt.Sprintf("book%d.epub", i))
		err := os.WriteFile(filePath, []byte(fmt.Sprintf("test content %d", i)), 0644)
		require.NoError(t, err)
	}

	// Submit directory scan job through WorkerInstance
	job := &services.Job{
		ID:   uuid.New().String(),
		Type: services.JobTypeDirectoryScan,
		Params: map[string]interface{}{
			"directory": tmpDir,
			"db":        setup.DB,
		},
		Status: services.JobStatusPending,
	}

	// Use EnqueueJob which returns errors
	err = services.WorkerInstance.EnqueueJob(job)
	require.NoError(t, err, "Failed to enqueue job")
	// Poll for job completion
	timeout := time.Now().Add(10 * time.Second)
	for time.Now().Before(timeout) {
		if status, exists := services.WorkerInstance.GetJobStatus(job.ID); exists {
			if status.Status == services.JobStatusCompleted || status.Status == services.JobStatusFailed {
				break
			}
		}
		time.Sleep(100 * time.Millisecond)
	}

	// Debug: Check job status after polling
	if finalStatus, exists := services.WorkerInstance.GetJobStatus(job.ID); exists {
		t.Logf("Job %s final status: %s, error: %s", job.ID, finalStatus.Status, finalStatus.Error)
	} else {
		t.Logf("Job %s not found in results", job.ID)
	}

	// Check that items were created in database
	ctx := context.Background()
	items, err := setup.DB.ListMediaItems(ctx, database.ListMediaItemsParams{
		Limit:  1000,
		Offset: 0,
	})
	require.NoError(t, err)

	// Should have at least 3 items (our test files)
	greaterOrEqual := func(a, b int) bool {
		return a >= b
	}
	assert.True(t, greaterOrEqual(len(items), 3), fmt.Sprintf("Expected at least 3 items, got %d", len(items)))
}

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

	token := setup.Token

	// Create library
	createLibReq := map[string]interface{}{
		"name":        "Set Folders Test Library",
		"description": "Test library for set folders",
		"type":        "ebooks",
	}
	createLibURL := setup.Server.URL + "/api/libraries"
	createLibReqHTTP, _ := http.NewRequest("POST", createLibURL, bytes.NewBuffer(jsonMarshal(createLibReq)))
	createLibReqHTTP.Header.Set("Content-Type", "application/json")
	createLibReqHTTP.Header.Set("Authorization", "Bearer "+token)

	client := &http.Client{}
	createLibResp, err := client.Do(createLibReqHTTP)
	require.NoError(t, err)
	require.Equal(t, http.StatusCreated, createLibResp.StatusCode)

	var createLibResponse map[string]interface{}
	err = json.NewDecoder(createLibResp.Body).Decode(&createLibResponse)
	require.NoError(t, err)
	_ = createLibResp.Body.Close()

	libraryID, ok := createLibResponse["id"].(string)
	require.True(t, ok)
	require.NotEmpty(t, libraryID)

	// Create temporary directory
	tmpDir := t.TempDir()

	// Add folder to library via HTTP API
	addFolderToLibrary(t, setup, libraryID, tmpDir)

	// Submit set folders job through WorkerInstance
	job := &services.Job{
		ID:   uuid.New().String(),
		Type: services.JobTypeSetFolders,
		Params: map[string]interface{}{
			"folders": []string{tmpDir},
			"db":      setup.DB,
		},
		Status: services.JobStatusPending,
	}

	err = services.WorkerInstance.EnqueueJob(job)
	require.NoError(t, err, "Failed to enqueue set folders job")

	// Wait for job to process
	time.Sleep(1 * time.Second)

	// Verify folder was added to library
	ctx := context.Background()
	folders, err := setup.DB.GetLibraryFolders(ctx, pgtype.UUID{Bytes: [16]byte(uuid.MustParse(libraryID)), Valid: true})
	require.NoError(t, err)
	assert.NotEmpty(t, folders, "Library should have folders")

	// Find our folder
	found := false
	for _, folder := range folders {
		if folder.FolderPath == tmpDir {
			found = true
			break
		}
	}
	assert.True(t, found, "Folder should be in library")
}

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

	ctx := context.Background()

	// Create a library and get admin user
	adminID := getTestUserID(t, setup.DB)

	libraryType, err := setup.DB.GetLibraryTypeByName(ctx, "ebooks")
	require.NoError(t, err)

	library, err := setup.DB.CreateLibrary(ctx, database.CreateLibraryParams{
		Name:             "Concurrent Job Test Library",
		Description:      pgtype.Text{String: "Test library", Valid: true},
		LibraryTypeID:    libraryType.ID,
		CreatedByAdminID: pgtype.UUID{Bytes: [16]byte(adminID), Valid: true},
	})
	require.NoError(t, err)

	// Create multiple temporary directories
	var tmpDirs []string
	for i := 0; i < 3; i++ {
		tmpDir := t.TempDir()
		tmpDirs = append(tmpDirs, tmpDir)

		// Add folder to library
		_, err = setup.DB.AddLibraryFolder(ctx, database.AddLibraryFolderParams{
			LibraryID:  library.ID,
			FolderPath: tmpDir,
		})
		require.NoError(t, err)

		// Create test files in each directory
		for j := 0; j < 2; j++ {
			filePath := filepath.Join(tmpDir, fmt.Sprintf("book%d.epub", j))
			err = os.WriteFile(filePath, []byte(fmt.Sprintf("test content %d", j)), 0644)
			require.NoError(t, err, "Failed to create test file")
		}
	}

	// Submit multiple concurrent jobs and track their IDs
	var jobs []*services.Job
	for _, tmpDir := range tmpDirs {
		job := &services.Job{
			ID:   uuid.New().String(),
			Type: services.JobTypeDirectoryScan,
			Params: map[string]interface{}{
				"directory": tmpDir,
				"db":        setup.DB,
			},
			Status: services.JobStatusPending,
		}
		jobs = append(jobs, job)
		err = services.WorkerInstance.EnqueueJob(job)
		require.NoError(t, err, "Failed to enqueue scan job")
	}

	// Wait for all jobs to complete
	timeout := time.Now().Add(15 * time.Second)
	completedJobs := 0
	for time.Now().Before(timeout) && completedJobs < 3 {
		for _, job := range jobs {
			if status, exists := services.WorkerInstance.GetJobStatus(job.ID); exists {
				if status.Status == services.JobStatusCompleted || status.Status == services.JobStatusFailed {
					completedJobs++
				}
			}
		}
		if completedJobs < 3 {
			time.Sleep(100 * time.Millisecond)
		}
	}

	// Verify all directories were processed
	allItems, err := setup.DB.ListMediaItems(ctx, database.ListMediaItemsParams{
		Limit:  1000,
		Offset: 0,
	})
	require.NoError(t, err)
	assert.NotEmpty(t, allItems, "Should have media items from concurrent jobs")
}

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

	ctx := context.Background()

	// Create test library
	adminID := getTestUserID(t, setup.DB)
	libraryType, err := setup.DB.GetLibraryTypeByName(ctx, "ebooks")
	require.NoError(t, err)

	library, err := setup.DB.CreateLibrary(ctx, database.CreateLibraryParams{
		Name:             "Job Status Test Library",
		Description:      pgtype.Text{String: "Test library", Valid: true},
		LibraryTypeID:    libraryType.ID,
		CreatedByAdminID: pgtype.UUID{Bytes: [16]byte(adminID), Valid: true},
	})
	require.NoError(t, err)

	tmpDir := t.TempDir()
	_, err = setup.DB.AddLibraryFolder(ctx, database.AddLibraryFolderParams{
		LibraryID:  library.ID,
		FolderPath: tmpDir,
	})
	require.NoError(t, err)

	// Submit job and track status
	jobID := uuid.New().String()
	job := &services.Job{
		ID:   jobID,
		Type: services.JobTypeDirectoryScan,
		Params: map[string]interface{}{
			"directory": tmpDir,
			"db":        setup.DB,
		},
		Status: services.JobStatusPending,
	}

	err = services.WorkerInstance.EnqueueJob(job)
	require.NoError(t, err, "Failed to enqueue set folders job")

	// Poll job status
	var finalStatus string
	for i := 0; i < 10; i++ {
		time.Sleep(500 * time.Millisecond)

		result, exists := services.WorkerInstance.GetJobStatus(jobID)
		if exists {
			finalStatus = string(result.Status)
			if result.Status == services.JobStatusCompleted || result.Status == services.JobStatusFailed {
				break
			}
		}
	}

	// Job should complete (success or failure)
	assert.True(t, finalStatus == "completed" || finalStatus == "failed",
		fmt.Sprintf("Job should complete, got status: %s", finalStatus))
}

// Helper function to marshal JSON
func jsonMarshal(v interface{}) []byte {
	b, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	return b
}
