package indexer

import (
	"context"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"
)

func TestProgressTracker_BasicFunctionality(t *testing.T) {
	// Create a new progress tracker
	var progressNotifications []ProgressNotification
	var completionNotifications []CompletionNotification
	var mu sync.Mutex

	config := &ProgressConfig{
		OnProgress: func(pn ProgressNotification) {
			mu.Lock()
			progressNotifications = append(progressNotifications, pn)
			mu.Unlock()
		},
		OnCompletion: func(cn CompletionNotification) {
			mu.Lock()
			completionNotifications = append(completionNotifications, cn)
			mu.Unlock()
		},
	}

	tracker := NewProgressTracker("/var/log/nginx/access.log", config)

	// Test adding files with various rotation formats
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.AddFile("/var/log/nginx/access.log.1", false)
	tracker.AddFile("/var/log/nginx/access.log.2.gz", true)
	tracker.AddFile("/var/log/nginx/access.1.log", false)
	tracker.AddFile("/var/log/nginx/access.2.log.gz", true)

	// Set estimates for files
	tracker.SetFileEstimate("/var/log/nginx/access.log", 1000)
	tracker.SetFileEstimate("/var/log/nginx/access.log.1", 2000)
	tracker.SetFileEstimate("/var/log/nginx/access.log.2.gz", 500)
	tracker.SetFileEstimate("/var/log/nginx/access.1.log", 800)
	tracker.SetFileEstimate("/var/log/nginx/access.2.log.gz", 300)

	// Test initial progress
	progress := tracker.GetProgress()
	if progress.Percentage != 0 {
		t.Errorf("Expected initial progress to be 0, got %.2f", progress.Percentage)
	}
	if progress.TotalFiles != 5 {
		t.Errorf("Expected 5 total files, got %d", progress.TotalFiles)
	}
	expectedTotal := int64(1000 + 2000 + 500 + 800 + 300) // 4600
	if progress.EstimatedLines != expectedTotal {
		t.Errorf("Expected %d estimated lines, got %d", expectedTotal, progress.EstimatedLines)
	}

	// Start processing first file
	tracker.StartFile("/var/log/nginx/access.log")
	progress = tracker.GetProgress()
	if progress.ProcessingFiles != 1 {
		t.Errorf("Expected 1 processing file, got %d", progress.ProcessingFiles)
	}

	// Update progress for first file
	tracker.UpdateFileProgress("/var/log/nginx/access.log", 500)
	progress = tracker.GetProgress()
	// With hybrid calculation: 40% line-based + 60% file-based
	// Line progress: 500/4600 = 10.87%, File progress: 0/5 = 0% (file still processing)
	// Expected: (10.87 * 0.4) + (0 * 0.6) = 4.35%
	expectedPercentage := (float64(500)/float64(4600)*100)*0.4 + (0.0)*0.6
	if progress.Percentage < expectedPercentage-1 || progress.Percentage > expectedPercentage+1 {
		t.Errorf("Expected progress around %.2f%%, got %.2f%%", expectedPercentage, progress.Percentage)
	}

	// Complete first file
	tracker.CompleteFile("/var/log/nginx/access.log", 1000)
	progress = tracker.GetProgress()
	if progress.CompletedFiles != 1 {
		t.Errorf("Expected 1 completed file, got %d", progress.CompletedFiles)
	}

	// Complete remaining files
	tracker.StartFile("/var/log/nginx/access.log.1")
	tracker.CompleteFile("/var/log/nginx/access.log.1", 2000)

	tracker.StartFile("/var/log/nginx/access.log.2.gz")
	tracker.CompleteFile("/var/log/nginx/access.log.2.gz", 500)

	tracker.StartFile("/var/log/nginx/access.1.log")
	tracker.CompleteFile("/var/log/nginx/access.1.log", 800)

	tracker.StartFile("/var/log/nginx/access.2.log.gz")
	tracker.CompleteFile("/var/log/nginx/access.2.log.gz", 300)

	// Wait a bit for notifications
	time.Sleep(10 * time.Millisecond)

	// Check final progress
	progress = tracker.GetProgress()
	if progress.Percentage != 100 {
		t.Errorf("Expected 100%% progress when all files complete, got %.2f%%", progress.Percentage)
	}
	if progress.CompletedFiles != 5 {
		t.Errorf("Expected 5 completed files, got %d", progress.CompletedFiles)
	}
	if !progress.IsCompleted {
		t.Errorf("Expected IsCompleted to be true")
	}

	// Verify notifications were sent
	mu.Lock()
	if len(completionNotifications) != 1 {
		t.Errorf("Expected 1 completion notification, got %d", len(completionNotifications))
	}
	mu.Unlock()
}

func TestProgressTracker_FileFailure(t *testing.T) {
	var completionNotifications []CompletionNotification
	var mu sync.Mutex

	config := &ProgressConfig{
		OnCompletion: func(cn CompletionNotification) {
			mu.Lock()
			completionNotifications = append(completionNotifications, cn)
			mu.Unlock()
		},
	}

	tracker := NewProgressTracker("/var/log/nginx/test.log", config)

	// Add files
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.AddFile("/var/log/nginx/error.log", false)

	// Start processing
	tracker.StartFile("/var/log/nginx/access.log")
	tracker.CompleteFile("/var/log/nginx/access.log", 100)

	tracker.StartFile("/var/log/nginx/error.log")
	tracker.FailFile("/var/log/nginx/error.log", "permission denied")

	// Wait for notifications
	time.Sleep(10 * time.Millisecond)

	// Check progress
	progress := tracker.GetProgress()
	if progress.FailedFiles != 1 {
		t.Errorf("Expected 1 failed file, got %d", progress.FailedFiles)
	}
	if progress.CompletedFiles != 1 {
		t.Errorf("Expected 1 completed file, got %d", progress.CompletedFiles)
	}
	if !progress.IsCompleted {
		t.Errorf("Expected IsCompleted to be true")
	}

	// Verify completion notification
	mu.Lock()
	if len(completionNotifications) != 1 {
		t.Errorf("Expected 1 completion notification, got %d", len(completionNotifications))
	}
	if len(completionNotifications) > 0 && completionNotifications[0].Success {
		t.Errorf("Expected completion notification to indicate failure")
	}
	mu.Unlock()
}

func TestProgressTracker_GetFileProgress(t *testing.T) {
	tracker := NewProgressTracker("/var/log/nginx/test.log", nil)

	// Add a file
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.SetFileEstimate("/var/log/nginx/access.log", 1000)
	tracker.SetFileSize("/var/log/nginx/access.log", 150000)

	// Get file progress
	fileProgress, exists := tracker.GetFileProgress("/var/log/nginx/access.log")
	if !exists {
		t.Error("Expected file progress to exist")
	}
	if fileProgress.EstimatedLines != 1000 {
		t.Errorf("Expected 1000 estimated lines, got %d", fileProgress.EstimatedLines)
	}
	if fileProgress.FileSize != 150000 {
		t.Errorf("Expected file size 150000, got %d", fileProgress.FileSize)
	}

	// Test non-existent file
	_, exists = tracker.GetFileProgress("/nonexistent.log")
	if exists {
		t.Error("Expected file progress to not exist for non-existent file")
	}
}

func TestProgressTracker_GetAllFiles(t *testing.T) {
	tracker := NewProgressTracker("/var/log/nginx/test.log", nil)

	// Add multiple files
	files := []string{
		"/var/log/nginx/access.log",
		"/var/log/nginx/error.log",
		"/var/log/nginx/ssl.log",
	}

	for _, file := range files {
		tracker.AddFile(file, false)
	}

	allFiles := tracker.GetAllFiles()
	if len(allFiles) != 3 {
		t.Errorf("Expected 3 files, got %d", len(allFiles))
	}

	for _, file := range files {
		if _, exists := allFiles[file]; !exists {
			t.Errorf("Expected file %s to exist in all files", file)
		}
	}
}

func TestProgressTracker_Cancel(t *testing.T) {
	var completionNotifications []CompletionNotification
	var mu sync.Mutex

	config := &ProgressConfig{
		OnCompletion: func(cn CompletionNotification) {
			mu.Lock()
			completionNotifications = append(completionNotifications, cn)
			mu.Unlock()
		},
	}

	tracker := NewProgressTracker("/var/log/nginx/test.log", config)

	// Add files
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.AddFile("/var/log/nginx/error.log", false)

	// Start one file
	tracker.StartFile("/var/log/nginx/access.log")

	// Cancel the tracker
	tracker.Cancel("user requested cancellation")

	// Wait for notifications
	time.Sleep(10 * time.Millisecond)

	// Check that all files are marked as failed
	allFiles := tracker.GetAllFiles()
	for _, file := range allFiles {
		if file.State != FileStateFailed {
			t.Errorf("Expected file %s to be in failed state after cancellation", file.FilePath)
		}
	}

	// Check completion
	if !tracker.IsCompleted() {
		t.Error("Expected tracker to be completed after cancellation")
	}

	// Verify completion notification
	mu.Lock()
	if len(completionNotifications) != 1 {
		t.Errorf("Expected 1 completion notification after cancellation, got %d", len(completionNotifications))
	}
	mu.Unlock()
}

func TestProgressTracker_NoNotificationWithoutConfig(t *testing.T) {
	// Create tracker without notification config
	tracker := NewProgressTracker("/var/log/nginx/test.log", nil)

	// Add and complete a file
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.StartFile("/var/log/nginx/access.log")
	tracker.CompleteFile("/var/log/nginx/access.log", 100)

	// Should not panic or cause issues
	progress := tracker.GetProgress()
	if !progress.IsCompleted {
		t.Error("Expected tracker to be completed")
	}
}

func TestProgressTracker_DuplicateCompletion(t *testing.T) {
	var completionNotifications []CompletionNotification
	var mu sync.Mutex

	config := &ProgressConfig{
		OnCompletion: func(cn CompletionNotification) {
			mu.Lock()
			completionNotifications = append(completionNotifications, cn)
			mu.Unlock()
		},
	}

	tracker := NewProgressTracker("/var/log/nginx/test.log", config)

	// Add a file
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.StartFile("/var/log/nginx/access.log")

	// Complete the file multiple times
	tracker.CompleteFile("/var/log/nginx/access.log", 100)
	tracker.CompleteFile("/var/log/nginx/access.log", 100)
	tracker.CompleteFile("/var/log/nginx/access.log", 100)

	// Wait for notifications
	time.Sleep(10 * time.Millisecond)

	// Should only get one completion notification
	mu.Lock()
	if len(completionNotifications) != 1 {
		t.Errorf("Expected 1 completion notification despite multiple complete calls, got %d", len(completionNotifications))
	}
	mu.Unlock()
}

func TestProgressTracker_NotificationThrottling(t *testing.T) {
	var progressNotifications []ProgressNotification
	var mu sync.Mutex

	config := &ProgressConfig{
		NotifyInterval: 100 * time.Millisecond, // Custom throttle interval
		OnProgress: func(pn ProgressNotification) {
			mu.Lock()
			progressNotifications = append(progressNotifications, pn)
			mu.Unlock()
		},
	}

	tracker := NewProgressTracker("/var/log/nginx/test.log", config)

	// Add a file
	tracker.AddFile("/var/log/nginx/access.log", false)
	tracker.SetFileEstimate("/var/log/nginx/access.log", 1000)
	tracker.StartFile("/var/log/nginx/access.log")

	// Send multiple rapid updates
	for i := 0; i < 10; i++ {
		tracker.UpdateFileProgress("/var/log/nginx/access.log", int64(i*10))
		time.Sleep(10 * time.Millisecond) // Faster than throttle interval
	}

	// Should have throttled notifications
	mu.Lock()
	notificationCount := len(progressNotifications)
	mu.Unlock()

	if notificationCount > 5 { // Should be significantly less than 10 due to throttling
		t.Errorf("Expected notifications to be throttled, got %d notifications", notificationCount)
	}
}

func TestEstimateFileLines(t *testing.T) {
	ctx := context.Background()

	// Test zero size file
	lines, err := EstimateFileLines(ctx, "/nonexistent/file", 0, false)
	if err != nil {
		t.Errorf("Expected no error for zero size file, got %v", err)
	}
	if lines != 0 {
		t.Errorf("Expected 0 lines for zero size file, got %d", lines)
	}

	// Test fallback for non-existent file
	lines, err = EstimateFileLines(ctx, "/nonexistent/file", 10000, false)
	if err != nil {
		t.Errorf("Expected no error for fallback estimation, got %v", err)
	}
	expected := int64(10000 / 150) // fallback estimate
	if lines != expected {
		t.Errorf("Expected %d lines for fallback estimate, got %d", expected, lines)
	}

	// Test context cancellation - since EstimateFileLines returns fallback for non-existent files,
	// we test cancellation by using a valid path but cancelling the context
	// For a non-existent file, it returns immediately with fallback, so no cancellation check
	cancelCtx, cancel := context.WithCancel(ctx)
	cancel() // Cancel immediately

	// For non-existent files, EstimateFileLines returns fallback estimate without error
	lines, err = EstimateFileLines(cancelCtx, "/nonexistent/file", 10000, false)
	if err != nil {
		t.Errorf("Expected no error for non-existent file even with cancelled context, got %v", err)
	}
	if lines != 10000/150 {
		t.Errorf("Expected fallback estimate for cancelled context with non-existent file")
	}
}

func TestEstimateFileLines_RealFile(t *testing.T) {
	// Create a temporary file with known content
	tmpDir := t.TempDir()
	testFile := filepath.Join(tmpDir, "test.log")

	// Create test content with known line count
	content := ""
	lineCount := 100
	for i := 0; i < lineCount; i++ {
		content += "This is a test log line with some content to make it realistic\n"
	}

	err := os.WriteFile(testFile, []byte(content), 0644)
	if err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}

	fileInfo, err := os.Stat(testFile)
	if err != nil {
		t.Fatalf("Failed to stat test file: %v", err)
	}

	ctx := context.Background()
	estimatedLines, err := EstimateFileLines(ctx, testFile, fileInfo.Size(), false)
	if err != nil {
		t.Errorf("Expected no error for real file estimation, got %v", err)
	}

	// Allow some tolerance in estimation (within 20% of actual)
	tolerance := float64(lineCount) * 0.2
	if float64(estimatedLines) < float64(lineCount)-tolerance ||
		float64(estimatedLines) > float64(lineCount)+tolerance {
		t.Errorf("Estimated lines %d not within tolerance of actual lines %d", estimatedLines, lineCount)
	}
}

func TestProgressManager(t *testing.T) {
	manager := NewProgressManager()

	// Test getting new tracker
	config := &ProgressConfig{}
	tracker1 := manager.GetTracker("/var/log/nginx/access.log", config)
	if tracker1 == nil {
		t.Error("Expected non-nil tracker")
	}

	// Test getting same tracker
	tracker2 := manager.GetTracker("/var/log/nginx/access.log", config)
	if tracker1 != tracker2 {
		t.Error("Expected same tracker instance for same path")
	}

	// Test getting different tracker
	tracker3 := manager.GetTracker("/var/log/nginx/error.log", config)
	if tracker1 == tracker3 {
		t.Error("Expected different tracker instance for different path")
	}

	// Test getting all trackers
	allTrackers := manager.GetAllTrackers()
	if len(allTrackers) != 2 {
		t.Errorf("Expected 2 trackers, got %d", len(allTrackers))
	}

	// Test removing tracker
	manager.RemoveTracker("/var/log/nginx/access.log")
	allTrackers = manager.GetAllTrackers()
	if len(allTrackers) != 1 {
		t.Errorf("Expected 1 tracker after removal, got %d", len(allTrackers))
	}

	// Test getting tracker after removal creates new one
	tracker4 := manager.GetTracker("/var/log/nginx/access.log", config)
	if tracker1 == tracker4 {
		t.Error("Expected new tracker instance after removal")
	}
}

func TestProgressManager_Cleanup(t *testing.T) {
	manager := NewProgressManager()

	// Add trackers
	config := &ProgressConfig{}
	tracker1 := manager.GetTracker("/var/log/nginx/access.log", config)
	tracker2 := manager.GetTracker("/var/log/nginx/error.log", config)

	// Complete one tracker
	tracker1.AddFile("/var/log/nginx/access.log", false)
	tracker1.StartFile("/var/log/nginx/access.log")
	tracker1.CompleteFile("/var/log/nginx/access.log", 100)

	// Leave the other incomplete
	tracker2.AddFile("/var/log/nginx/error.log", false)
	tracker2.StartFile("/var/log/nginx/error.log")

	// Cleanup
	manager.Cleanup()

	// Should have removed completed tracker
	allTrackers := manager.GetAllTrackers()
	if len(allTrackers) != 1 {
		t.Errorf("Expected 1 tracker after cleanup, got %d", len(allTrackers))
	}

	// Remaining tracker should be the incomplete one
	if _, exists := allTrackers["/var/log/nginx/error.log"]; !exists {
		t.Error("Expected incomplete tracker to remain after cleanup")
	}
}

func TestFileState_String(t *testing.T) {
	tests := []struct {
		state    FileState
		expected string
	}{
		{FileStatePending, "pending"},
		{FileStateProcessing, "processing"},
		{FileStateCompleted, "completed"},
		{FileStateFailed, "failed"},
		{FileState(999), "unknown"},
	}

	for _, test := range tests {
		if test.state.String() != test.expected {
			t.Errorf("Expected %s, got %s", test.expected, test.state.String())
		}
	}
}

func TestProgressTracker_ConcurrentAccess(t *testing.T) {
	tracker := NewProgressTracker("/var/log/nginx/test.log", nil)

	// Add multiple files
	for i := 0; i < 10; i++ {
		tracker.AddFile(filepath.Join("/var/log/nginx", "access"+string(rune(i))+".log"), false)
		tracker.SetFileEstimate(filepath.Join("/var/log/nginx", "access"+string(rune(i))+".log"), 1000)
	}

	var wg sync.WaitGroup

	// Simulate concurrent progress updates
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func(fileIndex int) {
			defer wg.Done()
			fileName := filepath.Join("/var/log/nginx", "access"+string(rune(fileIndex))+".log")

			tracker.StartFile(fileName)
			for j := 0; j < 100; j++ {
				tracker.UpdateFileProgress(fileName, int64(j*10))
				time.Sleep(time.Millisecond)
			}
			tracker.CompleteFile(fileName, 1000)
		}(i)
	}

	// Simulate concurrent progress reads
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < 50; j++ {
				_ = tracker.GetProgress()
				_ = tracker.GetAllFiles()
				time.Sleep(time.Millisecond)
			}
		}()
	}

	wg.Wait()

	// Verify final state
	progress := tracker.GetProgress()
	if progress.CompletedFiles != 10 {
		t.Errorf("Expected 10 completed files after concurrent access, got %d", progress.CompletedFiles)
	}
	if !progress.IsCompleted {
		t.Error("Expected tracker to be completed after concurrent access")
	}
}

func TestRotationLogSupport(t *testing.T) {
	// Test compression detection
	testCases := []struct {
		filePath   string
		compressed bool
	}{
		{"/var/log/nginx/access.log", false},
		{"/var/log/nginx/access.log.1", false},
		{"/var/log/nginx/access.log.2.gz", true},
		{"/var/log/nginx/access.1.log", false},
		{"/var/log/nginx/access.2.log.gz", true},
		{"/var/log/nginx/access.3.log.bz2", true},
		{"/var/log/nginx/error.log", false},
		{"/var/log/nginx/error.log.1.xz", true},
		{"/var/log/nginx/error.1.log.lz4", true},
	}

	for _, tc := range testCases {
		isCompressed := IsCompressedFile(tc.filePath)
		if isCompressed != tc.compressed {
			t.Errorf("IsCompressedFile(%s) = %v, expected %v", tc.filePath, isCompressed, tc.compressed)
		}
	}

	// Test rotation log detection
	rotationTests := []struct {
		filePath  string
		isRotation bool
	}{
		{"/var/log/nginx/access.log", true},
		{"/var/log/nginx/access.log.1", true},
		{"/var/log/nginx/access.log.2.gz", true},
		{"/var/log/nginx/access.1.log", true},
		{"/var/log/nginx/access.2.log.gz", true},
		{"/var/log/nginx/error.log", true},
		{"/var/log/nginx/error.log.10", true},
		{"/var/log/nginx/error.1.log", true},
		{"/var/log/nginx/not-a-log.txt", false},
		{"/var/log/nginx/access.json", false},
		{"/var/log/nginx/config.conf", false},
	}

	for _, tc := range rotationTests {
		isRotation := IsRotationLogFile(tc.filePath)
		if isRotation != tc.isRotation {
			t.Errorf("IsRotationLogFile(%s) = %v, expected %v", tc.filePath, isRotation, tc.isRotation)
		}
	}

	// Test AddRotationFiles convenience method
	tracker := NewProgressTracker("/var/log/nginx/access.log", nil)
	
	// Add multiple rotation files at once
	rotationFiles := []string{
		"/var/log/nginx/access.log",
		"/var/log/nginx/access.log.1",
		"/var/log/nginx/access.log.2.gz",
		"/var/log/nginx/access.1.log",
		"/var/log/nginx/access.2.log.gz",
	}
	
	tracker.AddRotationFiles(rotationFiles...)
	
	progress := tracker.GetProgress()
	if progress.TotalFiles != 5 {
		t.Errorf("Expected 5 files after AddRotationFiles, got %d", progress.TotalFiles)
	}

	// Verify compression was detected correctly
	files := tracker.GetAllFiles()
	expectedCompression := map[string]bool{
		"/var/log/nginx/access.log":       false,
		"/var/log/nginx/access.log.1":     false,
		"/var/log/nginx/access.log.2.gz":  true,
		"/var/log/nginx/access.1.log":     false,
		"/var/log/nginx/access.2.log.gz":  true,
	}

	for filePath, expectedComp := range expectedCompression {
		found := false
		for _, file := range files {
			if file.FilePath == filePath {
				found = true
				if file.IsCompressed != expectedComp {
					t.Errorf("File %s: expected compressed=%v, got compressed=%v", 
						filePath, expectedComp, file.IsCompressed)
				}
				break
			}
		}
		if !found {
			t.Errorf("File %s not found in tracker", filePath)
		}
	}
}